diff --git a/package.json b/package.json index fae3470..f6c8cc9 100644 --- a/package.json +++ b/package.json @@ -91,12 +91,12 @@ { "command": "markdowntable.nextCell", "key": "tab", - "when": "editorLangId == markdown && editorTextFocus && !editorReadonly && !editorTabMovesFocus && !suggestWidgetMultipleSuggestions && !suggestWidgetVisible && !inSnippetMode" + "when": "editorLangId == markdown && editorTextFocus && !editorReadonly && !editorTabMovesFocus && !suggestWidgetMultipleSuggestions && !suggestWidgetVisible && !inSnippetMode && selectionInMarkdownTable" }, { "command": "markdowntable.prevCell", "key": "shift+tab", - "when": "editorLangId == markdown && editorTextFocus && !editorReadonly && !editorTabMovesFocus && !suggestWidgetMultipleSuggestions && !suggestWidgetVisible && !inSnippetMode" + "when": "editorLangId == markdown && editorTextFocus && !editorReadonly && !editorTabMovesFocus && !suggestWidgetMultipleSuggestions && !suggestWidgetVisible && !inSnippetMode && selectionInMarkdownTable" }, { "command": "markdowntable.format", @@ -111,12 +111,12 @@ { "command": "markdowntable.nextCell", "key": "tab", - "when": "editorLangId == mdx && editorTextFocus && !editorReadonly && !editorTabMovesFocus && !suggestWidgetMultipleSuggestions && !suggestWidgetVisible && !inSnippetMode" + "when": "editorLangId == mdx && editorTextFocus && !editorReadonly && !editorTabMovesFocus && !suggestWidgetMultipleSuggestions && !suggestWidgetVisible && !inSnippetMode && selectionInMarkdownTable" }, { "command": "markdowntable.prevCell", "key": "shift+tab", - "when": "editorLangId == mdx && editorTextFocus && !editorReadonly && !editorTabMovesFocus && !suggestWidgetMultipleSuggestions && !suggestWidgetVisible && !inSnippetMode" + "when": "editorLangId == mdx && editorTextFocus && !editorReadonly && !editorTabMovesFocus && !suggestWidgetMultipleSuggestions && !suggestWidgetVisible && !inSnippetMode && selectionInMarkdownTable" } ], "menus": { diff --git a/src/commands.ts b/src/commands.ts new file mode 100644 index 0000000..7173562 --- /dev/null +++ b/src/commands.ts @@ -0,0 +1,520 @@ +import * as vscode from 'vscode'; +import * as mdt from './markdowntable'; +import MarkdownTableData from './markdownTableData'; +import * as text from './textUtility'; + + + +export function updateContextKey(statusBar :vscode.StatusBarItem) { + // エディタ取得 + const editor = vscode.window.activeTextEditor as vscode.TextEditor; + // ドキュメント取得 + const doc = editor.document; + // 選択範囲取得 + const cur_selection = editor.selection; + let inTable :boolean = true; + for(let linenum = cur_selection.start.line; linenum <= cur_selection.end.line; linenum++) { + const line_text = doc.getText(new vscode.Selection( + new vscode.Position(linenum, 0), + new vscode.Position(linenum, 10000))); + if(!text.isInTable(line_text)){ + inTable = false; + break; + } + } + + if (inTable) { + vscode.commands.executeCommand('setContext', 'selectionInMarkdownTable', true); + + // statusBar.text = `$(circle-large-filled) in the table`; + // statusBar.tooltip = `cursor is in the table`; + // statusBar.show(); + } else { + vscode.commands.executeCommand('setContext', 'selectionInMarkdownTable', false); + + // statusBar.text = `$(circle-slash) out of table`; + // statusBar.tooltip = `cursor is out of table`; + // statusBar.show(); + } +} + + +export function navigateNextCell(withFormat: boolean) { + console.log('navigateNextCell called!'); + + // エディタ取得 + const editor = vscode.window.activeTextEditor as vscode.TextEditor; + // ドキュメント取得 + const doc = editor.document; + // 選択範囲取得 + const cur_selection = editor.selection; + + // 表を探す + let startLine = cur_selection.anchor.line; + let endLine = cur_selection.anchor.line; + while (startLine - 1 >= 0) { + const line_selection = new vscode.Selection( + new vscode.Position(startLine - 1, 0), + new vscode.Position(startLine - 1, 10000)); + + const line_text = doc.getText(line_selection); + if (!text.isInTable(line_text)) { + break; + } + startLine--; + } + while (endLine + 1 < doc.lineCount) { + const line_selection = new vscode.Selection( + new vscode.Position(endLine + 1, 0), + new vscode.Position(endLine + 1, 10000)); + + const line_text = doc.getText(line_selection); + if (!text.isInTable(line_text)) { + break; + } + endLine++; + } + const table_selection = new vscode.Selection( + new vscode.Position(startLine, 0), + new vscode.Position(endLine, 10000)); + const table_text = doc.getText(table_selection); + + // 元のカーソル位置を取得 + const [prevline, prevcharacter] = [cur_selection.active.line - startLine, cur_selection.active.character]; + + // テーブルをTableDataにシリアライズ + let tableData = mdt.stringToTableData(table_text); + if (tableData.aligns[0][0] === undefined) { + return; + } + + // 元のカーソル位置のセルを取得 + const [prevRow, prevColumn] = mdt.getCellAtPosition(tableData, prevline, prevcharacter); + + // 次のセルが新しい行になるかどうか + const isNextRow = (prevColumn + 1 >= tableData.columns.length); + const isInsertNewRow = ( + // カラム行、または寄せ記号行の場合は3行目を作成する + (prevRow <= 1 && tableData.cells.length === 0) || + // 現在の行が最終行で、かつ次の行に進む場合は末尾に1行追加する + (isNextRow && prevRow >= tableData.cells.length + 1) + ); + + + // 次の行が必要なら追加する + if (isInsertNewRow === true) { + tableData = mdt.insertRow(tableData, tableData.cells.length); + } + + // テーブルをフォーマットしたテキストを取得 + const new_text = withFormat ? mdt.toFormatTableStr(tableData) : tableData.originalText; + const tableDataFormatted = mdt.stringToTableData(new_text); + + //エディタ選択範囲にテキストを反映 + editor.edit(edit => { + edit.replace(table_selection, new_text); + }); + + // 新しいカーソル位置を計算 + // character の +1 は表セル内の|とデータの間の半角スペース分 + const newColumn = (isNextRow === true) ? 0 : prevColumn + 1; + const newRow = (isNextRow === true) ? prevRow + 1 : prevRow; + const [newline, newcharacter] = mdt.getPositionOfCell(tableDataFormatted, newRow, newColumn); + const newPosition = new vscode.Position( + table_selection.start.line + newline, + table_selection.start.character + newcharacter + 1); + const newSelection = new vscode.Selection(newPosition, newPosition); + + // カーソル位置を移動 + editor.selection = newSelection; +}; + +export function navigatePrevCell(withFormat: boolean) { + console.log('navigatePrevCell called!'); + + // エディタ取得 + const editor = vscode.window.activeTextEditor as vscode.TextEditor; + // ドキュメント取得 + const doc = editor.document; + // 選択範囲取得 + const cur_selection = editor.selection; + + // 表を探す + let startLine = cur_selection.anchor.line; + let endLine = cur_selection.anchor.line; + while (startLine - 1 >= 0) { + const line_selection = new vscode.Selection( + new vscode.Position(startLine - 1, 0), + new vscode.Position(startLine - 1, 10000)); + + const line_text = doc.getText(line_selection); + if (!text.isInTable(line_text)) { + break; + } + startLine--; + } + while (endLine + 1 < doc.lineCount) { + const line_selection = new vscode.Selection( + new vscode.Position(endLine + 1, 0), + new vscode.Position(endLine + 1, 10000)); + + const line_text = doc.getText(line_selection); + if (!text.isInTable(line_text)) { + break; + } + endLine++; + } + const table_selection = new vscode.Selection( + new vscode.Position(startLine, 0), + new vscode.Position(endLine, 10000)); + let table_text = doc.getText(table_selection); + + + // 元のカーソル位置を取得 + const [prevline, prevcharacter] = [cur_selection.active.line - startLine, cur_selection.active.character]; + + // テーブルをTableDataにシリアライズ + let tableData = mdt.stringToTableData(table_text); + if (tableData.aligns[0][0] === undefined) { + return; + } + + // 元のカーソル位置のセルを取得 + const [prevRow, prevColumn] = mdt.getCellAtPosition(tableData, prevline, prevcharacter); + // 先頭セルだったら何もしない + if (prevColumn <= 0 && prevRow <= 0) { + return; + } + + // テーブルをフォーマットしたテキストを取得 + const new_text = withFormat ? mdt.toFormatTableStr(tableData) : tableData.originalText; + const tableDataFormatted = mdt.stringToTableData(new_text); + + //エディタ選択範囲にテキストを反映 + editor.edit(edit => { + edit.replace(table_selection, new_text); + }); + + // 新しいカーソル位置を計算 + // character の +1 は表セル内の|とデータの間の半角スペース分 + const newColumn = (prevColumn > 0) ? prevColumn - 1 : tableDataFormatted.columns.length - 1; + const newRow = (prevColumn > 0) ? prevRow : prevRow - 1; + const [newline, newcharacter] = mdt.getPositionOfCell(tableDataFormatted, newRow, newColumn); + let newPosition = new vscode.Position( + table_selection.start.line + newline, + table_selection.start.character + newcharacter); + if (withFormat || + mdt.getCellData(tableDataFormatted, newRow, newColumn).startsWith(' ')) { + newPosition = new vscode.Position( + table_selection.start.line + newline, + table_selection.start.character + newcharacter + 1); + } + const newSelection = new vscode.Selection(newPosition, newPosition); + + // カーソル位置を移動 + editor.selection = newSelection; +}; + +export function formatAll() { + // エディタ取得 + const editor = vscode.window.activeTextEditor as vscode.TextEditor; + // ドキュメント取得 + const doc = editor.document; + // // ドキュメント全てを取得する + // const all_selection = new vscode.Selection( + // new vscode.Position(0, 0), + // new vscode.Position(doc.lineCount - 1, 10000)); + + // const text = doc.getText(all_selection); //取得されたテキスト + // const lines = text.split(/\r\n|\n|\r/); + + // 変換のリスト + let format_list = [] as [vscode.Selection, MarkdownTableData][]; + + + // 表を探す + let preSearchedLine = -1; + for (let line = 0; line < doc.lineCount; line++) { + if (line <= preSearchedLine) { + continue; + } + if (!doc.lineAt(line).text.trim().startsWith('|')) { + continue; + } + let startLine = line; + let endLine = line; + + // 表の終わり行を探す + while (endLine + 1 < doc.lineCount && doc.lineAt(endLine + 1).text.trim().startsWith('|')) { + endLine++; + if (endLine >= doc.lineCount) { + break; + } + } + // 表のテキストを取得 + const table_selection = new vscode.Selection( + new vscode.Position(startLine, 0), + new vscode.Position(endLine, doc.lineAt(endLine).text.length)); + const table_text = doc.getText(table_selection); + + // 表をフォーマットする + const tableData = mdt.stringToTableData(table_text); + + // 変換内容をリストに保持する + format_list.push([table_selection, tableData]); + + preSearchedLine = endLine; + } + + // 新しいカーソル位置(editor.editでの処理が完了してから動かさないとずれるため外に置く) + let newSelection = new vscode.Selection(editor.selection.active, editor.selection.active); + + //エディタ選択範囲にテキストを反映 + editor.edit(edit => { + for (let i = 0; i < format_list.length; i++) { + const [selection, tableData] = format_list[i] as [vscode.Selection, MarkdownTableData]; + + // カーソルを元のセルと同じ位置にするためにカーソル位置を特定しておく + if (selection.contains(editor.selection.active)) { + // テーブルの変形処理クラス + const [prevline, prevcharacter] = [editor.selection.active.line - selection.start.line, editor.selection.active.character]; + const [prevRow, prevColumn] = mdt.getCellAtPosition(tableData, prevline, prevcharacter); + + // テキストを置換 + const tableStrFormatted = mdt.toFormatTableStr(tableData); + const tableDataFormatted = mdt.stringToTableData(tableStrFormatted); + + edit.replace(selection, tableStrFormatted); + + // 新しいカーソル位置を計算 + // character の +1 は表セル内の|とデータの間の半角スペース分 + const [newline, newcharacter] = mdt.getPositionOfCell(tableDataFormatted, prevRow, prevColumn); + const newPosition = new vscode.Position( + selection.start.line + newline, + selection.start.character + newcharacter + 1); + newSelection = new vscode.Selection(newPosition, newPosition); + } + else { + // テキストを置換 + edit.replace(selection, mdt.toFormatTableStr(tableData)); + } + } + }); + + // カーソル位置を移動 + editor.selection = newSelection; +} + +export function tsvToTable() { + // エディタ取得 + const editor = vscode.window.activeTextEditor as vscode.TextEditor; + // ドキュメント取得 + const doc = editor.document; + // 選択範囲取得 + const cur_selection = editor.selection; + if (editor.selection.isEmpty) { + return; + } + + const text = doc.getText(cur_selection); //取得されたテキスト + + const tableData = mdt.tsvToTableData(text); + const newTableStr = mdt.toFormatTableStr(tableData); + + //エディタ選択範囲にテキストを反映 + editor.edit(edit => { + edit.replace(cur_selection, newTableStr); + }); +} + +export function insertColumn(isLeft: boolean) { + // エディタ取得 + const editor = vscode.window.activeTextEditor as vscode.TextEditor; + // ドキュメント取得 + const doc = editor.document; + // 選択範囲取得 + const cur_selection = editor.selection; + if (!editor.selection.isEmpty) { + vscode.window.showErrorMessage('Markdown Table : Insert command doesn\'t allowed range selection.'); + return; + } + + // 表を探す + let startLine = cur_selection.anchor.line; + let endLine = cur_selection.anchor.line; + while (startLine - 1 >= 0) { + const line_selection = new vscode.Selection( + new vscode.Position(startLine - 1, 0), + new vscode.Position(startLine - 1, 10000)); + + const line_text = doc.getText(line_selection); + if (!text.isInTable(line_text)) { + break; + } + startLine--; + } + while (endLine + 1 < doc.lineCount) { + const line_selection = new vscode.Selection( + new vscode.Position(endLine + 1, 0), + new vscode.Position(endLine + 1, 10000)); + + const line_text = doc.getText(line_selection); + if (!text.isInTable(line_text)) { + break; + } + endLine++; + } + const table_selection = new vscode.Selection( + new vscode.Position(startLine, 0), + new vscode.Position(endLine, 10000)); + const table_text = doc.getText(table_selection); + + + // 元のカーソル位置を取得 + const [prevline, prevcharacter] = [cur_selection.active.line - startLine, cur_selection.active.character]; + + // テーブルをフォーマット + const tableData = mdt.stringToTableData(table_text); + + // 元のカーソル位置のセルを取得 + const [prevRow, prevColumn] = mdt.getCellAtPosition(tableData, prevline, prevcharacter); + + // 挿入位置 + const insertPosition = isLeft ? prevColumn : prevColumn + 1; + + const newTableData = mdt.insertColumn(tableData, insertPosition); + const tableStrFormatted = mdt.toFormatTableStr(newTableData); + const tableDataFormatted = mdt.stringToTableData(tableStrFormatted); + + //エディタ選択範囲にテキストを反映 + editor.edit(edit => { + edit.replace(table_selection, tableStrFormatted); + }); + + // 新しいカーソル位置を計算 + // character の +1 は表セル内の|とデータの間の半角スペース分 + const newColumn = insertPosition; + const [newline, newcharacter] = mdt.getPositionOfCell(tableDataFormatted, prevRow, newColumn); + const newPosition = new vscode.Position( + table_selection.start.line + newline, + table_selection.start.character + newcharacter + 1); + const newSelection = new vscode.Selection(newPosition, newPosition); + + // カーソル位置を移動 + editor.selection = newSelection; +}; + +export function alignColumns(alignMark: [string, string]) { + // エディタ取得 + const editor = vscode.window.activeTextEditor as vscode.TextEditor; + // ドキュメント取得 + const doc = editor.document; + // 選択範囲取得 + const cur_selection = editor.selection; + // 選択範囲の始まり行 + const currentLine = doc.getText(new vscode.Selection( + new vscode.Position(cur_selection.start.line, 0), + new vscode.Position(cur_selection.start.line, 10000))); + // テーブル内ではなかったら終了 + if (!currentLine.trim().startsWith('|')) { + vscode.window.showErrorMessage('Markdown Table : Align command failed, because your selection is not starting from inside of a table.'); + return; + } + + // 表を探す + let startLine = cur_selection.start.line; + let endLine = cur_selection.start.line; + while (startLine - 1 >= 0) { + const line_selection = new vscode.Selection( + new vscode.Position(startLine - 1, 0), + new vscode.Position(startLine - 1, 10000)); + + const line_text = doc.getText(line_selection); + if (!text.isInTable(line_text)) { + break; + } + startLine--; + } + while (endLine + 1 < doc.lineCount) { + const line_selection = new vscode.Selection( + new vscode.Position(endLine + 1, 0), + new vscode.Position(endLine + 1, 10000)); + + const line_text = doc.getText(line_selection); + if (!text.isInTable(line_text)) { + break; + } + endLine++; + } + if (endLine < cur_selection.end.line) { + vscode.window.showErrorMessage('Markdown Table : Align command failed, because your selection is hanging out of the table.'); + return; + } + const table_selection = new vscode.Selection( + new vscode.Position(startLine, 0), + new vscode.Position(endLine, 10000)); + const table_text = doc.getText(table_selection); + + + // テーブルをTableDataにシリアライズ + let tableData = mdt.stringToTableData(table_text); + if (tableData.aligns[0][0] === undefined) { + return; + } + + // 選択セルを取得 + const [startline, startcharacter] = [cur_selection.start.line - startLine, cur_selection.start.character]; + const [startRow, startColumn] = mdt.getCellAtPosition(tableData, startline, startcharacter); + const [endline, endcharacter] = [cur_selection.end.line - startLine, cur_selection.end.character]; + const [endRow, endColumn] = mdt.getCellAtPosition(tableData, endline, endcharacter); + + // 選択範囲の列のAlignを変更する + if (startRow === endRow) { + // 選択範囲の開始位置と終了位置が同じ行内の場合 + for (let column = startColumn; column <= endColumn; column++) { + tableData.aligns[column] = alignMark; + } + } + else if (startRow + 1 === endRow) { + // 選択範囲が2行にまたがる場合 + for (let column = startColumn; column <= tableData.columns.length; column++) { + tableData.aligns[column] = alignMark; + } + for (let column = 0; column <= endColumn; column++) { + tableData.aligns[column] = alignMark; + } + } + else { + // 選択範囲が3行以上にまたがる場合はすべての列が対象 + for (let column = 0; column < tableData.columns.length; column++) { + tableData.aligns[column] = alignMark; + } + } + + // テーブルをフォーマットした文字列を取得 + const newTableText = mdt.toFormatTableStr(tableData); + + //エディタ選択範囲にテキストを反映 + editor.edit(edit => { + edit.replace(table_selection, newTableText); + }); + + // 元のカーソル選択位置を計算 + const [anchorline, anchorcharacter] = [cur_selection.anchor.line - startLine, cur_selection.anchor.character]; + // 元のカーソル選択位置のセルを取得 + const [anchorRow, anchorColumn] = mdt.getCellAtPosition(tableData, anchorline, anchorcharacter,); + + const tableStrFormatted = mdt.toFormatTableStr(tableData); + const tableDataFormatted = mdt.stringToTableData(tableStrFormatted); + + // 新しいカーソル位置をフォーマット後のテキストから計算 + const [newline, newcharacter] = mdt.getPositionOfCell(tableDataFormatted, anchorRow, anchorColumn); + const newPosition = new vscode.Position( + table_selection.start.line + newline, + table_selection.start.character + newcharacter + 1); + const newSelection = new vscode.Selection(newPosition, newPosition); + + // カーソル位置を移動 + editor.selection = newSelection; +}; + diff --git a/src/extension.ts b/src/extension.ts index cf05bc5..2a1d01e 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -1,7 +1,11 @@ // The module 'vscode' contains the VS Code extensibility API // Import the module and reference it with the alias vscode in your code below import * as vscode from 'vscode'; -import * as markdowntable from './markdowntable'; +import * as commands from './commands'; + + +let myStatusBarItem: vscode.StatusBarItem; + // this method is called when your extension is activated // your extension is activated the very first time the command is executed @@ -15,560 +19,34 @@ export function activate(context: vscode.ExtensionContext) { // Now provide the implementation of the command with registerCommand // The commandId parameter must match the command field in package.json - function registerCommandNice(commandId: string, run: (...args: any[]) => void): void { - let command = vscode.commands.registerCommand(commandId, run); - context.subscriptions.push(command); - } - - let navigateNextCell = (withFormat :boolean) => { - // エディタ取得 - const editor = vscode.window.activeTextEditor as vscode.TextEditor; - // ドキュメント取得 - const doc = editor.document; - // 選択範囲取得 - const cur_selection = editor.selection; - // カーソル行 - const currentLine = new vscode.Selection( - new vscode.Position(cur_selection.active.line, 0), - new vscode.Position(cur_selection.active.line, 10000)); - const currentLineText = doc.getText(currentLine); - // テーブル内ではなかったら終了 - if (!currentLineText.trim().startsWith('|')) { - // 通常のインデント - vscode.commands.executeCommand('tab'); - return; - } - - // 表を探す - let startLine = cur_selection.anchor.line; - let endLine = cur_selection.anchor.line; - while (startLine - 1 >= 0) { - const line_selection = new vscode.Selection( - new vscode.Position(startLine - 1, 0), - new vscode.Position(startLine - 1, 10000)); - - const line_text = doc.getText(line_selection); - if (!line_text.trim().startsWith('|')) { - break; - } - startLine--; - } - while (endLine + 1 < doc.lineCount) { - const line_selection = new vscode.Selection( - new vscode.Position(endLine + 1, 0), - new vscode.Position(endLine + 1, 10000)); - - const line_text = doc.getText(line_selection); - if (!line_text.trim().startsWith('|')) { - break; - } - endLine++; - } - const table_selection = new vscode.Selection( - new vscode.Position(startLine, 0), - new vscode.Position(endLine, 10000)); - const table_text = doc.getText(table_selection); - - - // テーブルの変形処理クラス - const mdt = new markdowntable.MarkdownTable(); - - // 元のカーソル位置を取得 - const [prevline, prevcharacter] = [cur_selection.active.line - startLine, cur_selection.active.character]; - - // テーブルをTableDataにシリアライズ - let tableData = mdt.stringToTableData(table_text); - if (tableData.aligns[0][0] === undefined) { - return; - } - - // 元のカーソル位置のセルを取得 - const [prevRow, prevColumn] = tableData.getCellAtPosition(prevline, prevcharacter, false); - - // 次のセルが新しい行になるかどうか - const isNextRow = (prevColumn + 1 >= tableData.columns.length); - const isInsertNewRow = ( - // カラム行、または寄せ記号行の場合は3行目を作成する - (prevRow <= 1 && tableData.cells.length === 0) || - // 現在の行が最終行で、かつ次の行に進む場合は末尾に1行追加する - (isNextRow && prevRow >= tableData.cells.length + 1) - ); - - - // 次の行が必要なら追加する - if (isInsertNewRow === true) { - tableData = mdt.insertRow(tableData, tableData.cells.length); - } - - // テーブルをフォーマットしたテキストを取得 - let formatted_text = withFormat ? tableData.toFormatTableStr() : tableData.toString(); - - //エディタ選択範囲にテキストを反映 - editor.edit(edit => { - edit.replace(table_selection, formatted_text); - }); - - // 新しいカーソル位置を計算 - // character の +1 は表セル内の|とデータの間の半角スペース分 - const newColumn = (isNextRow === true) ? 0 : prevColumn + 1; - const newRow = (isNextRow === true) ? prevRow + 1 : prevRow; - const [newline, newcharacter] = tableData.getPositionOfCell(newRow, newColumn, withFormat); - const newPosition = new vscode.Position( - table_selection.start.line + newline, - table_selection.start.character + newcharacter + 1); - const newSelection = new vscode.Selection(newPosition, newPosition); - - // カーソル位置を移動 - editor.selection = newSelection; - }; - - let navigatePrevCell = (withFormat :boolean) => { - // エディタ取得 - const editor = vscode.window.activeTextEditor as vscode.TextEditor; - // ドキュメント取得 - const doc = editor.document; - // 選択範囲取得 - const cur_selection = editor.selection; - // カーソル行 - const currentLine = doc.getText(new vscode.Selection( - new vscode.Position(cur_selection.active.line, 0), - new vscode.Position(cur_selection.active.line, 10000))); - // テーブル内ではなかったら終了 - if (!currentLine.trim().startsWith('|')) { - // 通常のアウトデント - vscode.commands.executeCommand('outdent'); - return; - } - - // 表を探す - let startLine = cur_selection.anchor.line; - let endLine = cur_selection.anchor.line; - while (startLine - 1 >= 0) { - const line_selection = new vscode.Selection( - new vscode.Position(startLine - 1, 0), - new vscode.Position(startLine - 1, 10000)); - - const line_text = doc.getText(line_selection); - if (!line_text.trim().startsWith('|')) { - break; - } - startLine--; - } - while (endLine + 1 < doc.lineCount) { - const line_selection = new vscode.Selection( - new vscode.Position(endLine + 1, 0), - new vscode.Position(endLine + 1, 10000)); - - const line_text = doc.getText(line_selection); - if (!line_text.trim().startsWith('|')) { - break; - } - endLine++; - } - const table_selection = new vscode.Selection( - new vscode.Position(startLine, 0), - new vscode.Position(endLine, 10000)); - let table_text = doc.getText(table_selection); - - - - // テーブルの変形処理クラス - const mdt = new markdowntable.MarkdownTable(); - - // 元のカーソル位置を取得 - const [prevline, prevcharacter] = [cur_selection.active.line - startLine, cur_selection.active.character]; - - // テーブルをTableDataにシリアライズ - let tableData = mdt.stringToTableData(table_text); - if (tableData.aligns[0][0] === undefined) { - return; - } - - // 元のカーソル位置のセルを取得 - const [prevRow, prevColumn] = tableData.getCellAtPosition(prevline, prevcharacter, false); - // 先頭セルだったら何もしない - if (prevColumn <= 0 && prevRow <= 0) { - return; - } - - // テーブルをフォーマットしたテキストを取得 - let formatted_text = withFormat ? tableData.toFormatTableStr() : tableData.toString(); - - //エディタ選択範囲にテキストを反映 - editor.edit(edit => { - edit.replace(table_selection, formatted_text); - }); - - // 新しいカーソル位置を計算 - // character の +1 は表セル内の|とデータの間の半角スペース分 - const newColumn = (prevColumn > 0) ? prevColumn - 1 : tableData.columns.length - 1; - const newRow = (prevColumn > 0) ? prevRow : prevRow -1; - const [newline, newcharacter] = tableData.getPositionOfCell(newRow, newColumn, withFormat); - let newPosition = new vscode.Position( - table_selection.start.line + newline, - table_selection.start.character + newcharacter); - if( withFormat || - tableData.getCellData(newRow, newColumn).startsWith(' ')) { - newPosition = new vscode.Position( - table_selection.start.line + newline, - table_selection.start.character + newcharacter + 1); - } - const newSelection = new vscode.Selection(newPosition, newPosition); - - // カーソル位置を移動 - editor.selection = newSelection; - }; - - registerCommandNice('markdowntable.nextCell', (args) => { - navigateNextCell(true); - }); - - registerCommandNice('markdowntable.prevCell', (args) => { - navigatePrevCell(true); - }); - - registerCommandNice('markdowntable.nextCellWithoutFormat', (args) => { - navigateNextCell(false); - }); - - registerCommandNice('markdowntable.prevCellWithoutFormat', (args) => { - navigatePrevCell(false); - }); - - registerCommandNice('markdowntable.tsvToTable', () => { - // The code you place here will be executed every time your command is executed - - // エディタ取得 - const editor = vscode.window.activeTextEditor as vscode.TextEditor; - // ドキュメント取得 - const doc = editor.document; - // 選択範囲取得 - const cur_selection = editor.selection; - if (editor.selection.isEmpty) { - return; - } - - const text = doc.getText(cur_selection); //取得されたテキスト - - const mdt = new markdowntable.MarkdownTable(); - const tableData = mdt.tsvToTableData(text); - const newTableStr = tableData.toFormatTableStr(); - - //エディタ選択範囲にテキストを反映 - editor.edit(edit => { - edit.replace(cur_selection, newTableStr); - }); - }); - - registerCommandNice('markdowntable.format', () => { - // The code you place here will be executed every time your command is executed - - // エディタ取得 - const editor = vscode.window.activeTextEditor as vscode.TextEditor; - // ドキュメント取得 - const doc = editor.document; - // // ドキュメント全てを取得する - // const all_selection = new vscode.Selection( - // new vscode.Position(0, 0), - // new vscode.Position(doc.lineCount - 1, 10000)); - - // const text = doc.getText(all_selection); //取得されたテキスト - // const lines = text.split(/\r\n|\n|\r/); - - // 変換のリスト - let format_list = [] as [vscode.Selection, markdowntable.TableData][]; - - // テーブルの変形処理クラス - const mdt = new markdowntable.MarkdownTable(); - - - // 表を探す - let preSearchedLine = -1; - for (let line = 0; line < doc.lineCount; line++) { - if (line <= preSearchedLine) { - continue; - } - if (!doc.lineAt(line).text.trim().startsWith('|')) { - continue; - } - let startLine = line; - let endLine = line; - - // 表の終わり行を探す - while (endLine + 1 < doc.lineCount && doc.lineAt(endLine + 1).text.trim().startsWith('|')) { - endLine++; - if (endLine >= doc.lineCount) { - break; - } - } - // 表のテキストを取得 - const table_selection = new vscode.Selection( - new vscode.Position(startLine, 0), - new vscode.Position(endLine, doc.lineAt(endLine).text.length)); - const table_text = doc.getText(table_selection); - - // 表をフォーマットする - const tableData = mdt.stringToTableData(table_text); - - // 変換内容をリストに保持する - format_list.push([table_selection, tableData]); - - preSearchedLine = endLine; - } - - // 新しいカーソル位置(editor.editでの処理が完了してから動かさないとずれるため外に置く) - let newSelection = new vscode.Selection(editor.selection.active, editor.selection.active); - - //エディタ選択範囲にテキストを反映 - editor.edit(edit => { - for (let i = 0; i < format_list.length; i++) { - const [selection, tableData] = format_list[i] as [vscode.Selection, markdowntable.TableData]; - - // カーソルを元のセルと同じ位置にするためにカーソル位置を特定しておく - if (selection.contains(editor.selection.active)) { - // テーブルの変形処理クラス - const [prevline, prevcharacter] = [editor.selection.active.line - selection.start.line, editor.selection.active.character]; - const [prevRow, prevColumn] = tableData.getCellAtPosition(prevline, prevcharacter, false); - - // テキストを置換 - const tableStrFormatted = tableData.toFormatTableStr(); - edit.replace(selection, tableStrFormatted); - - // 新しいカーソル位置を計算 - // character の +1 は表セル内の|とデータの間の半角スペース分 - const [newline, newcharacter] = tableData.getPositionOfCell(prevRow, prevColumn, true); - const newPosition = new vscode.Position( - selection.start.line + newline, - selection.start.character + newcharacter + 1); - newSelection = new vscode.Selection(newPosition, newPosition); - } - else { - // テキストを置換 - edit.replace(selection, tableData.toFormatTableStr()); - } - } - }); - - // カーソル位置を移動 - editor.selection = newSelection; - - }); - - let insertColumn = (isLeft: boolean) => { - // エディタ取得 - const editor = vscode.window.activeTextEditor as vscode.TextEditor; - // ドキュメント取得 - const doc = editor.document; - // 選択範囲取得 - const cur_selection = editor.selection; - if (!editor.selection.isEmpty) { - vscode.window.showErrorMessage('Markdown Table : Insert command doesn\'t allowed range selection.'); - return; - } - - // 表を探す - let startLine = cur_selection.anchor.line; - let endLine = cur_selection.anchor.line; - while (startLine - 1 >= 0) { - const line_selection = new vscode.Selection( - new vscode.Position(startLine - 1, 0), - new vscode.Position(startLine - 1, 10000)); - - const line_text = doc.getText(line_selection); - if (!line_text.trim().startsWith('|')) { - break; - } - startLine--; - } - while (endLine + 1 < doc.lineCount) { - const line_selection = new vscode.Selection( - new vscode.Position(endLine + 1, 0), - new vscode.Position(endLine + 1, 10000)); - - const line_text = doc.getText(line_selection); - if (!line_text.trim().startsWith('|')) { - break; - } - endLine++; - } - const table_selection = new vscode.Selection( - new vscode.Position(startLine, 0), - new vscode.Position(endLine, 10000)); - const table_text = doc.getText(table_selection); - - - // テーブルの変形処理クラス - const mdt = new markdowntable.MarkdownTable(); - - // 元のカーソル位置を取得 - const [prevline, prevcharacter] = [cur_selection.active.line - startLine, cur_selection.active.character]; - - // テーブルをフォーマット - const tableData = mdt.stringToTableData(table_text); - - // 元のカーソル位置のセルを取得 - const [prevRow, prevColumn] = tableData.getCellAtPosition(prevline, prevcharacter, false); - - // 挿入位置 - const insertPosition = isLeft ? prevColumn : prevColumn + 1; - - const newTableData = mdt.insertColumn(tableData, insertPosition); - const newTableText = newTableData.toFormatTableStr(); - - //エディタ選択範囲にテキストを反映 - editor.edit(edit => { - edit.replace(table_selection, newTableText); - }); - - // 新しいカーソル位置を計算 - // character の +1 は表セル内の|とデータの間の半角スペース分 - const newColumn = insertPosition; - const [newline, newcharacter] = newTableData.getPositionOfCell(prevRow, newColumn, true); - const newPosition = new vscode.Position( - table_selection.start.line + newline, - table_selection.start.character + newcharacter + 1); - const newSelection = new vscode.Selection(newPosition, newPosition); - - // カーソル位置を移動 - editor.selection = newSelection; - }; - - registerCommandNice('markdowntable.insertRight', () => { - // The code you place here will be executed every time your command is executed - insertColumn(false); - }); - - registerCommandNice('markdowntable.insertLeft', () => { - // The code you place here will be executed every time your command is executed - insertColumn(true); - }); - - - let alignColumns = (alignMark: [string, string]) => { - // エディタ取得 - const editor = vscode.window.activeTextEditor as vscode.TextEditor; - // ドキュメント取得 - const doc = editor.document; - // 選択範囲取得 - const cur_selection = editor.selection; - // 選択範囲の始まり行 - const currentLine = doc.getText(new vscode.Selection( - new vscode.Position(cur_selection.start.line, 0), - new vscode.Position(cur_selection.start.line, 10000))); - // テーブル内ではなかったら終了 - if (!currentLine.trim().startsWith('|')) { - vscode.window.showErrorMessage('Markdown Table : Align command failed, because your selection is not starting from inside of a table.'); - return; - } - - // 表を探す - let startLine = cur_selection.start.line; - let endLine = cur_selection.start.line; - while (startLine - 1 >= 0) { - const line_selection = new vscode.Selection( - new vscode.Position(startLine - 1, 0), - new vscode.Position(startLine - 1, 10000)); - - const line_text = doc.getText(line_selection); - if (!line_text.trim().startsWith('|')) { - break; - } - startLine--; - } - while (endLine + 1 < doc.lineCount) { - const line_selection = new vscode.Selection( - new vscode.Position(endLine + 1, 0), - new vscode.Position(endLine + 1, 10000)); - - const line_text = doc.getText(line_selection); - if (!line_text.trim().startsWith('|')) { - break; - } - endLine++; - } - if (endLine < cur_selection.end.line) { - vscode.window.showErrorMessage('Markdown Table : Align command failed, because your selection is hanging out of the table.'); - return; - } - const table_selection = new vscode.Selection( - new vscode.Position(startLine, 0), - new vscode.Position(endLine, 10000)); - const table_text = doc.getText(table_selection); - - // テーブルの変形処理クラス - const mdt = new markdowntable.MarkdownTable(); - - // テーブルをTableDataにシリアライズ - let tableData = mdt.stringToTableData(table_text); - if (tableData.aligns[0][0] === undefined) { - return; - } - - // 選択セルを取得 - const [startline, startcharacter] = [cur_selection.start.line - startLine, cur_selection.start.character]; - const [startRow, startColumn] = tableData.getCellAtPosition(startline, startcharacter, false); - const [endline, endcharacter] = [cur_selection.end.line - startLine, cur_selection.end.character]; - const [endRow, endColumn] = tableData.getCellAtPosition(endline, endcharacter, false); - - // 選択範囲の列のAlignを変更する - if (startRow === endRow) { - // 選択範囲の開始位置と終了位置が同じ行内の場合 - for (let column = startColumn; column <= endColumn; column++) { - tableData.aligns[column] = alignMark; - } - } - else if (startRow + 1 === endRow) { - // 選択範囲が2行にまたがる場合 - for (let column = startColumn; column <= tableData.columns.length; column++) { - tableData.aligns[column] = alignMark; - } - for (let column = 0; column <= endColumn; column++) { - tableData.aligns[column] = alignMark; - } - } - else { - // 選択範囲が3行以上にまたがる場合はすべての列が対象 - for (let column = 0; column < tableData.columns.length; column++) { - tableData.aligns[column] = alignMark; - } - } - - // テーブルをフォーマットした文字列を取得 - const newTableText = tableData.toFormatTableStr(); - - //エディタ選択範囲にテキストを反映 - editor.edit(edit => { - edit.replace(table_selection, newTableText); - }); - - // 元のカーソル選択位置を計算 - const [anchorline, anchorcharacter] = [cur_selection.anchor.line - startLine, cur_selection.anchor.character]; - // 元のカーソル選択位置のセルを取得 - const [anchorRow, anchorColumn] = tableData.getCellAtPosition(anchorline, anchorcharacter, false); - - // 新しいカーソル位置をフォーマット後のテキストから計算 - const [newline, newcharacter] = tableData.getPositionOfCell(anchorRow, anchorColumn, true); - const newPosition = new vscode.Position( - table_selection.start.line + newline, - table_selection.start.character + newcharacter + 1); - const newSelection = new vscode.Selection(newPosition, newPosition); - - // カーソル位置を移動 - editor.selection = newSelection; - }; - - registerCommandNice('markdowntable.alignLeft', () => { - alignColumns([':', '-']); - }); - - registerCommandNice('markdowntable.alignCenter', () => { - alignColumns([':', ':']); - }); - - registerCommandNice('markdowntable.alignRight', () => { - alignColumns(['-', ':']); - }); + // set a custom context key + vscode.commands.executeCommand('setContext', 'selectionInMarkdownTable', false); + // subscribe custome handlers updating context key status + context.subscriptions.push( + vscode.window.onDidChangeActiveTextEditor(() => commands.updateContextKey(myStatusBarItem)), + vscode.window.onDidChangeTextEditorSelection(() => commands.updateContextKey(myStatusBarItem)) + ); + + // create a new status bar item that we can now manage + myStatusBarItem = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left, 10000); + context.subscriptions.push(myStatusBarItem); + + // subscribe command handlers + context.subscriptions.push( + vscode.commands.registerCommand('markdowntable.nextCell', () => commands.navigateNextCell(true)), + vscode.commands.registerCommand('markdowntable.prevCell', () => commands.navigatePrevCell(true)), + vscode.commands.registerCommand('markdowntable.nextCellWithoutFormat', () => commands.navigateNextCell(false)), + vscode.commands.registerCommand('markdowntable.prevCellWithoutFormat', () => commands.navigatePrevCell(false)), + vscode.commands.registerCommand('markdowntable.tsvToTable', () => commands.tsvToTable()), + vscode.commands.registerCommand('markdowntable.format', () => commands.formatAll()), + vscode.commands.registerCommand('markdowntable.insertRight', () => commands.insertColumn(false)), + vscode.commands.registerCommand('markdowntable.insertLeft', () => commands.insertColumn(true)), + vscode.commands.registerCommand('markdowntable.alignLeft', () => commands.alignColumns([':', '-'])), + vscode.commands.registerCommand('markdowntable.alignCenter', () => commands.alignColumns([':', ':'])), + vscode.commands.registerCommand('markdowntable.alignRight', () => commands.alignColumns(['-', ':'])) + ); } + // this method is called when your extension is deactivated export function deactivate() { } diff --git a/src/markdownTableData.ts b/src/markdownTableData.ts new file mode 100644 index 0000000..f7c935b --- /dev/null +++ b/src/markdownTableData.ts @@ -0,0 +1,18 @@ + +export default class MarkdownTableData { + public readonly originalText: string; + public readonly aligns: [string, string][]; + public readonly columns: string[]; + public readonly cells: string[][]; + public readonly leftovers: string[]; + public readonly indent: string; + + constructor(_text: string, _aligns: [string, string][], _columns: string[], _cells: string[][], _leftovers: string[], _indent: string) { + this.originalText = _text; + this.aligns = _aligns; + this.columns = _columns; + this.cells = _cells; + this.leftovers = _leftovers; + this.indent = _indent; + } +}; diff --git a/src/markdownTableUtility.ts b/src/markdownTableUtility.ts new file mode 100644 index 0000000..6aac73d --- /dev/null +++ b/src/markdownTableUtility.ts @@ -0,0 +1,152 @@ + + +/** +* 1行分の文字列を列データ配列に分解する +* 指定した列数に満たない行は 埋め文字 で埋める +* @param linestr 1行分の文字列 +* @param columnNum 列数 +* @param fillstr 埋め文字 +*/ +export function splitline(linestr: string, columnNum: number, fillstr: string = '') { + // 先頭と末尾の|を削除 + linestr = linestr.trim(); + if (linestr.startsWith('|')) { + linestr = linestr.slice(1); + } + if (linestr.endsWith('|')) { + linestr = linestr.slice(0, -1); + } + + // |で分割 + let linedatas: string[] = []; + let startindex = 0; + let endindex = 0; + let isEscaping = false; + let isInInlineCode = false; + for (let i = 0; i < linestr.length; ++i) { + if (isEscaping) { + // エスケープ文字の次の文字は|かどうか判定しない + isEscaping = false; + endindex++; + continue; + } + + const chara = linestr.charAt(i); + if (chara === '\`') { + // `の間はインラインコード + isInInlineCode = !isInInlineCode; + endindex++; + continue; + } + if (isInInlineCode) { + // インラインコード中は|かどうか判定しない + endindex++; + continue; + } + + if (chara === '\\') { + // \はエスケープ文字 + isEscaping = true; + endindex++; + continue; + } + + if (chara !== '|') { + // | 以外だったら継続 + endindex++; + continue; + } + + // | だったら分割 + let cellstr = linestr.slice(startindex, endindex); + linedatas.push(cellstr); + startindex = i + 1; + endindex = i + 1; + } + linedatas.push(linestr.slice(startindex)); + + // データ数分を''で埋めておく + let datas: string[] = new Array(columnNum).fill(fillstr); + // 行文字列から取得したデータに置き換える + for (let i = 0; i < linedatas.length; i++) { + datas[i] = linedatas[i]; + } + return datas; +}; + + + +// 半角文字は1文字、全角文字は2文字として文字数をカウントする +export function getLen(str: string): number { + let length = 0; + for (let i = 0; i < str.length; i++) { + let chp = str.codePointAt(i); + if (chp === undefined) { + continue; + } + let chr = chp as number; + if (doesUse0Space(chr)) { + length += 0; + } + else if (doesUse3Spaces(chr)) { + length += 3; + } + else if (doesUse2Spaces(chr)) { + // 全角文字の場合は2を加算 + length += 2; + } + else { + //それ以外の文字の場合は1を加算 + length += 1; + } + + let chc = str.charCodeAt(i); + if (chc >= 0xD800 && chc <= 0xDBFF) { + // サロゲートペアの時は1文字読み飛ばす + i++; + } + + // if( (chr >= 0x00 && chr <= 0x80) || + // (chr >= 0xa0 && chr <= 0xff) || + // (chr === 0xf8f0) || + // (chr >= 0xff61 && chr <= 0xff9f) || + // (chr >= 0xf8f1 && chr <= 0xf8f3)){ + // //半角文字の場合は1を加算 + // length += 1; + // }else{ + // //それ以外の文字の場合は2を加算 + // length += 2; + // } + } + //結果を返す + return length; +}; + +function doesUse0Space(charCode: number): boolean { + if ((charCode === 0x02DE) || + (charCode >= 0x0300 && charCode <= 0x036F) || + (charCode >= 0x0483 && charCode <= 0x0487) || + (charCode >= 0x0590 && charCode <= 0x05CF)) { + return true; + } + return false; +} + +function doesUse2Spaces(charCode: number): boolean { + if ((charCode >= 0x2480 && charCode <= 0x24FF) || + (charCode >= 0x2600 && charCode <= 0x27FF) || + (charCode >= 0x2900 && charCode <= 0x2CFF) || + (charCode >= 0x2E00 && charCode <= 0xFF60) || + (charCode >= 0xFFA0)) { + return true; + } + return false; +} + +function doesUse3Spaces(charCode: number): boolean { + if (charCode >= 0x1F300 && charCode <= 0x1FBFF) { + return true; + } + return false; +} + diff --git a/src/markdowntable.ts b/src/markdowntable.ts index 7aa08fc..71d9ee0 100644 --- a/src/markdowntable.ts +++ b/src/markdowntable.ts @@ -1,556 +1,373 @@ import { workspace } from "vscode"; +import MarkdownTableData from './markdownTableData'; +import * as Utility from './markdownTableUtility'; -// 行文字列をセルデータ配列に分解する -// datasNumMin に指定したデータ数に満たない行は '' で埋める -function splitline(linestr: string, datasNumMin :number, fillstr :string = '') { - // 先頭と末尾の|を削除 - linestr = linestr.trim(); - if (linestr.startsWith('|')) { - linestr = linestr.slice(1); - } - if (linestr.endsWith('|')) { - linestr = linestr.slice(0, -1); - } - // |で分割 - let linedatas : string[] = []; - let startindex = 0; - let endindex = 0; - let isEscaping = false; - let isInInlineCode = false; - for (let i = 0; i < linestr.length; ++i) { - const chara = linestr.charAt(i); - if(chara === '\\') { - // \はエスケープ文字 - isEscaping = true; - endindex++; - continue; - } - if(isEscaping) { - // エスケープ文字の次の文字は|かどうか判定しない - isEscaping = false; - endindex++; - continue; - } +/** +* テーブルを表すマークダウンテキストを MarkdownTableData に変換する +* @param tableText テーブルを表すマークダウンテキスト +*/ +export function stringToTableData(tableText: string): MarkdownTableData { + let lines = tableText.split(/\r\n|\n|\r/); - if(chara === '\`') { - // `の間はインラインコード - isInInlineCode = !isInInlineCode; - endindex++; - continue; + let getIndent = (linestr: string) => { + if (linestr.trim().startsWith('|')) { + let linedatas = linestr.split('|'); + return linedatas[0]; } - if(isInInlineCode) { - // インラインコード中は|かどうか判定しない - endindex++; - continue; + else { + return ''; } + }; - if(chara !== '|') { - // | 以外だったら継続 - endindex++; - continue; - } + // 1行目 + let columns = Utility.splitline(lines[0], 0); + let columnNum = columns.length; + let indent = getIndent(lines[0]); + + // 2行目の寄せ記号 + let aligns: [string, string][] = new Array(); + let aligndatas = Utility.splitline(lines[1], columnNum, '---').map((v) => v.trim()); + for (let i = 0; i < columnNum; i++) { + let celldata = aligndatas[i]; + aligns[i] = [celldata[0], celldata.slice(-1)]; + } - // | だったら分割 - let cellstr = linestr.slice(startindex, endindex); - linedatas.push(cellstr); - startindex = i+1; - endindex = i+1; + // セルの値を取得 + let cells: string[][] = new Array(); + let leftovers: string[] = new Array(); + let cellrow = -1; + for (let row = 2; row < lines.length; row++) { + cellrow++; + + let linedatas = Utility.splitline(lines[row], columnNum); + cells[cellrow] = linedatas.slice(0, columnNum); + + // あまりデータを収集する + leftovers[cellrow] = ''; + if (linedatas.length > columnNum) { + let leftoverdatas = linedatas.slice(columnNum, linedatas.length); + leftovers[cellrow] = leftoverdatas.join('|'); + } } - linedatas.push(linestr.slice(startindex)); - // 最低データ数分を''で埋めておく - let datas : string[] = new Array(datasNumMin).fill(fillstr); - // 行文字列から取得したデータに置き換える - for (let i = 0; i < linedatas.length; i++) { - datas[i] = linedatas[i]; + return new MarkdownTableData(tableText, aligns, columns, cells, leftovers, indent); +} + +/** +* タブ区切りテキストを MarkdownTableData に変換する +* @param tableText タブ区切りテキスト +*/ +export function tsvToTableData(tsvText: string): MarkdownTableData { + // 入力データを行ごとに分割する + let lines = tsvText.split(/\r\n|\n|\r/); + // カラムデータ + let columns: string[] = new Array(); + let columntexts = lines[0].split('\t'); + // カラム数 + let columnCount = columntexts.length; + + for (let i = 0; i < columnCount; i++) { + columns[i] = columntexts[i].trim(); } - return datas; -}; + // 入力データから改行とタブで分割した2次元配列を生成する + let cells: string[][] = new Array(); + // カラム数よりもはみ出たデータ + let leftovers: string[] = new Array(); + for (let row = 1; row < lines.length; row++) { + // 各セルの値 + cells[row - 1] = new Array(); + // 行内のデータが足りない場合に備えて空白文字で埋める + for (let column = 0; column < columnCount; column++) { + cells[row - 1][column] = ' '; + } + // 余りデータを初期化 + leftovers[row - 1] = ''; -export class TableData { - public readonly aligns : [string, string][] ; - public readonly columns : string[]; - public readonly cells: string [][]; - public readonly leftovers : string[]; - public readonly indent : string; + // 行データをタブで分割 + let lineValues = lines[row].split('\t'); - constructor(_text :string, _aligns: [string, string][], _columns : string[], _cells: string [][], _leftovers : string[], _indent : string){ - this.aligns = _aligns; - this.columns = _columns; - this.cells = _cells; - this.leftovers = _leftovers; - this.indent = _indent; + // 実際の値に置き換える + for (let column = 0; column < lineValues.length; column++) { + if (column >= columnCount) { + // カラムヘッダーよりも多い場合ははみ出しデータ配列に保存 + leftovers[row - 1] += '\t' + lineValues[column]; + continue; + } + cells[row - 1][column] = lineValues[column].trim(); + } } - public toString() :string { - //return this.originalText; + // 表の寄せ記号 + let aligns: [string, string][] = new Array(); + for (let column = 0; column < columnCount; column++) { + // 全部左寄せ + aligns[column] = [':', '-']; + } - let tableString = ""; + const table = new MarkdownTableData("", aligns, columns, cells, leftovers, ''); + return new MarkdownTableData(toFormatTableStr(table), aligns, columns, cells, leftovers, ''); +} - // カラムヘッダー行の作成 - tableString += this.indent; - for (let i = 0; i < this.columns.length; i++) { - tableString += '|' + this.columns[i]; - } - tableString += '|\r\n'; - // テーブル記号 - tableString += this.indent; - for (let i = 0; i < this.columns.length; i++) { - let [front, end] = this.aligns[i]; - tableString += '| ' + front + '-' + end + ' '; - } - tableString += '|\r\n'; - // テーブル内の各行 - for (let row = 0; row < this.cells.length; row++) { - tableString += this.indent; - for (let i = 0; i < this.cells[row].length; i++) { - tableString += '|' + this.cells[row][i]; - } - tableString += '|'; - // 余りデータがある場合はつなげる - if (this.leftovers[row] !== '') { - tableString += this.leftovers[row]; - } +/** + * MarkdownTableData に行を追加 + * @param tableData + * @param insertAt + * @returns + */ +export function insertRow(tableData: MarkdownTableData, insertAt: number): MarkdownTableData { + const columns = tableData.columns; + const aligns = tableData.aligns; + const cells = tableData.cells; + const leftovers = tableData.leftovers; + const column_num = tableData.columns.length; + const indent = tableData.indent; - // 次の行がある場合は改行を付ける - if (row+1 < this.cells.length) { - tableString += '\r\n'; - } - } + cells.splice(insertAt, 0, Array.from({ length: column_num }, () => ' ')); + leftovers.splice(insertAt, 0, ''); + + const text = tableData.originalText + '\n' + tableData.indent + '|' + ' |'.repeat(tableData.columns.length); + + return new MarkdownTableData(text, aligns, columns, cells, leftovers, indent); +} - return tableString; +export function insertColumn(tableData: MarkdownTableData, insertAt: number): MarkdownTableData { + let columns = tableData.columns; + let aligns = tableData.aligns; + let cells = tableData.cells; + let leftovers = tableData.leftovers; + let column_num = tableData.columns.length; + let indent = tableData.indent; + + columns.splice(insertAt, 0, ''); + aligns.splice(insertAt, 0, ['-', '-']); + for (let i = 0; i < cells.length; i++) { + cells[i].splice(insertAt, 0, ''); } - public toFormatTableStr() :string { - let alignData = workspace.getConfiguration('markdowntable').get('alignData'); - let alignHeader = workspace.getConfiguration('markdowntable').get('alignColumnHeader'); - let columnNum = this.columns.length; - - // 各列の最大文字数を調べる - let maxWidths : number[] = new Array(); - // コラムヘッダーの各項目の文字数 - for (let i = 0; i < this.columns.length; i++) { - let cellLength = this.getLen(this.columns[i].trim()); - // 表の寄せ記号行は最短で半角3文字なので、各セル最低でも半角3文字 - maxWidths[i] = (3 > cellLength) ? 3 : cellLength; - } + const table = new MarkdownTableData("", aligns, columns, cells, leftovers, indent); + return new MarkdownTableData(toFormatTableStr(table), aligns, columns, cells, leftovers, indent); +} - for (let row = 0; row < this.cells.length; row++) { - let cells = this.cells[row]; - for (let i = 0; i < cells.length; i++) { - if (i > columnNum) { break; } - let cellLength = this.getLen(cells[i].trim()); - maxWidths[i] = (maxWidths[i] > cellLength) ? maxWidths[i] : cellLength; - } - } - let formatted : string[] = new Array(); - - // 列幅をそろえていく - for (let row = 0; row < this.cells.length; row++) { - formatted[row] = ''; - formatted[row] += this.indent; - let cells = this.cells[row]; - for (let i = 0; i < columnNum; i++) { - let celldata = ''; - if (i < cells.length) { - celldata = cells[i].trim(); - } - let celldata_length = this.getLen(celldata); - - // | の後にスペースを入れる - formatted[row] += '| '; - if (alignData) { - let [front, end] = this.aligns[i]; - if (front === ':' && end === ':') { - // 中央ぞろえ - for(let n = 0; n < (maxWidths[i] - celldata_length) / 2 - 0.5; n++) { - formatted[row] += ' '; - } - formatted[row] += celldata; - for(let n = 0; n < (maxWidths[i] - celldata_length) / 2; n++) { - formatted[row] += ' '; - } - } - else if (front === '-' && end === ':') { - // 右揃え - for(let n = 0; n < maxWidths[i] - celldata_length; n++) { - formatted[row] += ' '; - } - formatted[row] += celldata; - } - else { - // 左揃え - formatted[row] += celldata; - for(let n = 0; n < maxWidths[i] - celldata_length; n++) { - formatted[row] += ' '; - } - } - } - else { - // データ - formatted[row] += celldata; - // 余白を半角スペースで埋める - for(let n = celldata_length; n < maxWidths[i]; n++) { - formatted[row] += ' '; - } - } - // | の前にスペースを入れる - formatted[row] += ' '; - } - formatted[row] += '|'; - // あまりデータを末尾に着ける - if (this.leftovers[row].length > 0) { - formatted[row] += this.leftovers[row]; - } +export function toFormatTableStr(tableData: MarkdownTableData): string { + let alignData = workspace.getConfiguration('markdowntable').get('alignData'); + let alignHeader = workspace.getConfiguration('markdowntable').get('alignColumnHeader'); + let columnNum = tableData.columns.length; + + // 各列の最大文字数を調べる + let maxWidths: number[] = new Array(); + // コラムヘッダーの各項目の文字数 + for (let i = 0; i < tableData.columns.length; i++) { + let cellLength = Utility.getLen(tableData.columns[i].trim()); + // 表の寄せ記号行は最短で半角3文字なので、各セル最低でも半角3文字 + maxWidths[i] = (3 > cellLength) ? 3 : cellLength; + } + + for (let row = 0; row < tableData.cells.length; row++) { + let cells = tableData.cells[row]; + for (let i = 0; i < cells.length; i++) { + if (i > columnNum) { break; } + let cellLength = Utility.getLen(cells[i].trim()); + maxWidths[i] = (maxWidths[i] > cellLength) ? maxWidths[i] : cellLength; } + } - // 1行目を成形する - let columnHeader = ''; - columnHeader += this.indent; + let formatted: string[] = new Array(); + + // 列幅をそろえていく + for (let row = 0; row < tableData.cells.length; row++) { + formatted[row] = ''; + formatted[row] += tableData.indent; + let cells = tableData.cells[row]; for (let i = 0; i < columnNum; i++) { - const columnText = this.columns[i].trim(); - const columnHeader_length = this.getLen(columnText); + let celldata = ''; + if (i < cells.length) { + celldata = cells[i].trim(); + } + let celldata_length = Utility.getLen(celldata); - columnHeader += '| '; - if (alignHeader) { - let [front, end] = this.aligns[i]; + // | の後にスペースを入れる + formatted[row] += '| '; + if (alignData) { + let [front, end] = tableData.aligns[i]; if (front === ':' && end === ':') { // 中央ぞろえ - for(let n = 0; n < (maxWidths[i] - columnHeader_length) / 2 - 0.5; n++) { - columnHeader += ' '; + for (let n = 0; n < (maxWidths[i] - celldata_length) / 2 - 0.5; n++) { + formatted[row] += ' '; } - columnHeader += columnText; - for(let n = 0; n < (maxWidths[i] - columnHeader_length) / 2; n++) { - columnHeader += ' '; + formatted[row] += celldata; + for (let n = 0; n < (maxWidths[i] - celldata_length) / 2; n++) { + formatted[row] += ' '; } } else if (front === '-' && end === ':') { // 右揃え - for(let n = 0; n < maxWidths[i] - columnHeader_length; n++) { - columnHeader += ' '; + for (let n = 0; n < maxWidths[i] - celldata_length; n++) { + formatted[row] += ' '; } - columnHeader += columnText; + formatted[row] += celldata; } else { // 左揃え - columnHeader += columnText; - for(let n = 0; n < maxWidths[i] - columnHeader_length; n++) { - columnHeader += ' '; + formatted[row] += celldata; + for (let n = 0; n < maxWidths[i] - celldata_length; n++) { + formatted[row] += ' '; } } - } else { - columnHeader += columnText; - // 余白を-で埋める - for(let n = columnHeader_length; n < maxWidths[i]; n++) { - columnHeader += ' '; + // データ + formatted[row] += celldata; + // 余白を半角スペースで埋める + for (let n = celldata_length; n < maxWidths[i]; n++) { + formatted[row] += ' '; } } - columnHeader += ' '; + // | の前にスペースを入れる + formatted[row] += ' '; } - columnHeader += '|'; + formatted[row] += '|'; - - // 2行目を成形する - let tablemark = ''; - tablemark += this.indent; - for (let i = 0; i < columnNum; i++) { - let [front, end] = this.aligns[i]; - tablemark += '| ' + front; - - // 余白を-で埋める - for(let n = 1; n < maxWidths[i] - 1; n++) { - tablemark += '-'; - } - tablemark += end + ' '; + // あまりデータを末尾に着ける + if (tableData.leftovers[row].length > 0) { + formatted[row] += tableData.leftovers[row]; } - tablemark += '|'; - - formatted.splice(0, 0, columnHeader); - formatted.splice(1, 0, tablemark); - - return formatted.join('\r\n'); } - // 半角文字は1文字、全角文字は2文字として文字数をカウントする - public getLen(str :string) :number { - let length = 0; - for(let i=0; i= 0xD800 && chc <= 0xDBFF) { - // サロゲートペアの時は1文字読み飛ばす - i++; + // 左揃え + columnHeader += columnText; + for (let n = 0; n < maxWidths[i] - columnHeader_length; n++) { + columnHeader += ' '; + } } - // if( (chr >= 0x00 && chr <= 0x80) || - // (chr >= 0xa0 && chr <= 0xff) || - // (chr === 0xf8f0) || - // (chr >= 0xff61 && chr <= 0xff9f) || - // (chr >= 0xf8f1 && chr <= 0xf8f3)){ - // //半角文字の場合は1を加算 - // length += 1; - // }else{ - // //それ以外の文字の場合は2を加算 - // length += 2; - // } } - //結果を返す - return length; - }; - - private doesUse0Space(charCode :number): boolean { - if ((charCode === 0x02DE) || - (charCode >= 0x0300 && charCode <= 0x036F) || - (charCode >= 0x0483 && charCode <= 0x0487) || - (charCode >= 0x0590 && charCode <= 0x05CF) ) { - return true; - } - return false; - } - - private doesUse2Spaces(charCode :number): boolean { - if ((charCode >= 0x2480 && charCode <= 0x24FF) || - (charCode >= 0x2600 && charCode <= 0x27FF) || - (charCode >= 0x2900 && charCode <= 0x2CFF) || - (charCode >= 0x2E00 && charCode <= 0xFF60) || - (charCode >= 0xFFA0) ) { - return true; - } - return false; - } - - private doesUse3Spaces(charCode :number): boolean { - if (charCode >= 0x1F300 && charCode <= 0x1FBFF) { - return true; + else { + columnHeader += columnText; + // 余白を-で埋める + for (let n = columnHeader_length; n < maxWidths[i]; n++) { + columnHeader += ' '; + } } - return false; + columnHeader += ' '; } + columnHeader += '|'; + // 2行目を成形する + let tablemark = ''; + tablemark += tableData.indent; + for (let i = 0; i < columnNum; i++) { + let [front, end] = tableData.aligns[i]; + tablemark += '| ' + front; - // return [line, character] - public getPositionOfCell(cellRow :number, cellColumn :number, isInFormatStr :boolean) : [number, number] { - let line = (cellRow <= 0) ? 0 : cellRow; - - let lines = isInFormatStr ? this.toFormatTableStr().split(/\r\n|\n|\r/) : this.toString().split(/\r\n|\n|\r/); - let linestr = lines[cellRow]; - - let cells = splitline(linestr, this.columns.length); - - let character = 0; - character += this.indent.length; - character += 1; - for (let i = 0; i < cellColumn; i++) { - character += cells[i].length; - character += 1; + // 余白を-で埋める + for (let n = 1; n < maxWidths[i] - 1; n++) { + tablemark += '-'; } - - return [line, character]; + tablemark += end + ' '; } + tablemark += '|'; - // return [row, column] - public getCellAtPosition(line :number, character :number, isInFormatStr :boolean) { - let row = (line <= 0) ? 0 : line; - - let lines = isInFormatStr ? this.toFormatTableStr().split(/\r\n|\n|\r/) : this.toString().split(/\r\n|\n|\r/); - let linestr = lines[row]; - - let cells = splitline(linestr, this.columns.length); - - let column = -1; - let cell_end = this.indent.length; - for (let cell of cells) { - column ++; - cell_end += 1 + cell.length; + formatted.splice(0, 0, columnHeader); + formatted.splice(1, 0, tablemark); - if(character <= cell_end) { - break; - } - } + return formatted.join('\r\n'); +} - return [row, column]; - } - public getCellData(cellRow :number, cellColumn :number) : string { - if(cellRow === 0) { - return (this.columns.length > cellColumn) ? this.columns[cellColumn] : ""; - } - if(cellRow === 1) { - if(this.aligns.length <= cellColumn) { - return "---"; - } - let [front, end] = this.aligns[cellColumn]; - return ' ' + front + '-' + end + ' '; - } - if(cellRow >= this.cells.length+2) { - return ""; - } - return this.cells[cellRow-2][cellColumn]; - } -}; - -export class MarkdownTable { - public stringToTableData(tableText :string) :TableData { - let lines = tableText.split(/\r\n|\n|\r/); - - let getIndent = (linestr: string) => { - if (linestr.trim().startsWith('|')) { - let linedatas = linestr.split('|'); - return linedatas[0]; - } - else { - return ''; - } - }; +// return [line, character] +export function getPositionOfCell(tableData: MarkdownTableData, cellRow: number, cellColumn: number): [number, number] { + let line = (cellRow <= 0) ? 0 : cellRow; - // 1行目 - let columns = splitline(lines[0], 0); - let columnNum = columns.length; - let indent = getIndent(lines[0]); + let lines = tableData.originalText.split(/\r\n|\n|\r/); + let linestr = lines[cellRow]; - // 2行目の寄せ記号 - let aligns : [string, string][] = new Array(); - let aligndatas = splitline(lines[1], columnNum, '---').map((v)=> v.trim()); - for (let i = 0; i < columnNum; i++) { - let celldata = aligndatas[i]; - aligns[i] = [celldata[0], celldata.slice(-1)]; - } + let cells = Utility.splitline(linestr, tableData.columns.length); - // セルの値を取得 - let cells : string [][] = new Array(); - let leftovers : string[] = new Array(); - let cellrow = -1; - for (let row = 2; row < lines.length; row++) { - cellrow++; - - let linedatas = splitline(lines[row], columnNum); - cells[cellrow] = linedatas.slice(0, columnNum); - - // あまりデータを収集する - leftovers[cellrow] = ''; - if (linedatas.length > columnNum) - { - let leftoverdatas = linedatas.slice(columnNum, linedatas.length); - leftovers[cellrow] = leftoverdatas.join('|'); - } - } - - return new TableData(tableText, aligns, columns, cells, leftovers, indent); + let character = 0; + character += tableData.indent.length; + character += 1; + for (let i = 0; i < cellColumn; i++) { + character += cells[i].length; + character += 1; } - public tsvToTableData(srcText :string) : TableData { - // 入力データを行ごとに分割する - let lines = srcText.split(/\r\n|\n|\r/); - // カラムデータ - let columns : string[] = new Array(); - let columntexts = lines[0].split('\t'); - // カラム数 - let columnCount = columntexts.length; - - for (let i = 0; i < columnCount; i++) { - columns[i] = columntexts[i].trim(); - } + return [line, character]; +} - // 入力データから改行とタブで分割した2次元配列を生成する - let cells: string [][] = new Array(); - // カラム数よりもはみ出たデータ - let leftovers : string[] = new Array(); - for (let row = 1; row < lines.length; row++) { - // 各セルの値 - cells[row - 1] = new Array(); - // 行内のデータが足りない場合に備えて空白文字で埋める - for (let column = 0; column < columnCount; column++) { - cells[row - 1][column] = ' '; - } +// return [row, column] +export function getCellAtPosition(tableData: MarkdownTableData, line: number, character: number): [number, number] { + let row = (line <= 0) ? 0 : line; - // 余りデータを初期化 - leftovers[row - 1] = ''; + let lines = tableData.originalText.split(/\r\n|\n|\r/); + let linestr = lines[row]; - // 行データをタブで分割 - let lineValues = lines[row].split('\t'); + let cells = Utility.splitline(linestr, tableData.columns.length); - // 実際の値に置き換える - for (let column = 0; column < lineValues.length; column++) { - if(column >= columnCount){ - // カラムヘッダーよりも多い場合ははみ出しデータ配列に保存 - leftovers[row - 1] += '\t' + lineValues[column]; - continue; - } - cells[row - 1][column] = lineValues[column].trim(); - } - } + let column = -1; + let cell_end = tableData.indent.length; + for (let cell of cells) { + column++; + cell_end += 1 + cell.length; - // 表の寄せ記号 - let aligns : [string, string][] = new Array(); - for (let column = 0; column < columnCount; column++) { - // 全部左寄せ - aligns[column] = [':', '-']; + if (character <= cell_end) { + break; } - - const table = new TableData("", aligns, columns, cells, leftovers, ''); - return new TableData(table.toFormatTableStr(), aligns, columns, cells, leftovers, ''); } - public insertRow(tableData :TableData, insertAt :number) : TableData { - const columns = tableData.columns; - const aligns = tableData.aligns; - const cells = tableData.cells; - const leftovers = tableData.leftovers; - const column_num = tableData.columns.length; - const indent = tableData.indent; - - cells.splice(insertAt, 0, Array.from({length: column_num}, () => ' ')); - leftovers.splice(insertAt, 0, ''); - - const text = tableData.toString() + '\n' + tableData.indent + '|' + ' |'.repeat(tableData.columns.length); + return [row, column]; +} - return new TableData(text, aligns, columns, cells, leftovers, indent); +export function getCellData(tableData: MarkdownTableData, cellRow: number, cellColumn: number): string { + if (cellRow === 0) { + return (tableData.columns.length > cellColumn) ? tableData.columns[cellColumn] : ""; } - - public insertColumn(tableData :TableData, insertAt :number) : TableData { - let columns = tableData.columns; - let aligns = tableData.aligns; - let cells = tableData.cells; - let leftovers = tableData.leftovers; - let column_num = tableData.columns.length; - let indent = tableData.indent; - - columns.splice(insertAt, 0, ''); - aligns.splice(insertAt, 0, ['-', '-']); - for (let i = 0; i < cells.length; i++) - { - cells[i].splice(insertAt, 0, ''); + if (cellRow === 1) { + if (tableData.aligns.length <= cellColumn) { + return "---"; } - - const table = new TableData("", aligns, columns, cells, leftovers, indent); - return new TableData(table.toFormatTableStr(), aligns, columns, cells, leftovers, indent); + let [front, end] = tableData.aligns[cellColumn]; + return ' ' + front + '-' + end + ' '; + } + if (cellRow >= tableData.cells.length + 2) { + return ""; } + + return tableData.cells[cellRow - 2][cellColumn]; } + diff --git a/src/test/sample/table.md b/src/test/sample/table.md index ae6dc75..8f3ab0b 100644 --- a/src/test/sample/table.md +++ b/src/test/sample/table.md @@ -1,11 +1,11 @@ # - | column A | column B \| `|` | | column C | | column D | | - | :------- | :-------------- | :-- | :------- | :-- | ----------: | --- | - | data A1 | data B1 | | data C1 | | \| data D1 | | - | data A2 | data B2 | | data C2 | | `|`data D2 | | - | data A3 | data B3 | | data C3 | | `|` data D3 | | - | data A4 | data B4 | | data C4 | | `data| D4` | | + | column A | column B \| `|` | | column C | `\\` | column D | | + | :------- | :-------------- | :----- | :------------- | :--- | ----------: | --- | + | data A1 | data B1 | | data C1 | | \| data D1 | | + | data A2 | data B2 `\\` | `\A\\` | \`\\`` data C2 | | `|`data D2 | | + | data A3 | data B3 | | data C3 | | `|` data D3 | | + | data A4 | data B4 | | data C4 | | `data| D4` | | | column A | column B | column C | column D | diff --git a/src/textUtility.ts b/src/textUtility.ts new file mode 100644 index 0000000..3dd880e --- /dev/null +++ b/src/textUtility.ts @@ -0,0 +1,4 @@ + +export function isInTable(text: string) :boolean { + return text.trim().startsWith('|'); +} \ No newline at end of file