|
1 | | -import React, { Component } from 'react' |
| 1 | +import { Component } from "react"; |
2 | 2 | import { observer } from 'mobx-react' |
3 | | -import debounce from 'lodash/debounce' |
4 | | -import { autorun } from 'mobx' |
5 | | -import { FsSocketClient } from 'backendAPI/websocketClients' |
6 | | -import api from 'backendAPI' |
| 3 | +import subscribeToSearch from 'commons/Search/subscribeToSearch' |
| 4 | +import state from 'commons/Search/state' |
7 | 5 | import cx from 'classnames' |
8 | | -import icons from 'file-icons-js' |
9 | | - |
10 | | -import { notify, NOTIFY_TYPE } from 'components/Notification/actions' |
11 | 6 | import { openFile } from 'commands/commandBindings/file' |
12 | | -import { createTab, activateTab } from 'components/Tab/actions' |
13 | | -import FileTreeState from 'components/FileTree/state' |
14 | | -import dispatchCommand from 'commands/dispatchCommand' |
15 | | -import state from './state' |
| 7 | +import * as delegate from 'commons/Search/action' |
| 8 | +import icons from 'file-icons-js' |
| 9 | + |
| 10 | +export class SearchResultItem extends Component { |
| 11 | + constructor(props) { |
| 12 | + super(props); |
| 13 | + this.state = { |
| 14 | + isFolded: false |
| 15 | + }; |
| 16 | + } |
16 | 17 |
|
17 | | -class SearchResultItem extends Component { |
18 | | - constructor (props) { |
19 | | - super(props) |
20 | | - this.state = { |
21 | | - isFolded: false |
| 18 | + handlePathClick = () => { |
| 19 | + this.setState({ |
| 20 | + isFolded: !this.state.isFolded |
| 21 | + }) |
22 | 22 | } |
23 | | - } |
24 | | - handlePathClick = () => { |
25 | | - this.setState({ |
26 | | - isFolded: !this.state.isFolded |
27 | | - }) |
28 | | - } |
29 | | - handleItemClick = (path, line) => { |
30 | | - const selection = new monaco.Selection( |
31 | | - line.line, |
32 | | - line.indexes[0] + 1, |
33 | | - line.line, |
34 | | - line.indexes[0] + this.props.keyword.length + 1, |
35 | | - ) |
36 | 23 |
|
37 | | - openFile({ path, editor: { filePath: path, selection } }) |
38 | | - // const fileTreeNode = FileTreeState.entities.get(path) |
39 | | - // if (fileTreeNode) { |
40 | | - // dispatchCommand('file:open_file', { |
41 | | - // path: fileTreeNode.path, |
42 | | - // editor: { filePath: path, selection }, |
43 | | - // }) |
44 | | - // } else { |
45 | | - // openFile({ path, editor: { filePath: path, selection } }) |
46 | | - // } |
47 | | - } |
48 | | - render () { |
49 | | - const { path, value } = this.props |
50 | | - const iconStr = 'file-icon ' + (icons.getClassWithColor(path) || 'fa fa-file-text-o') |
51 | | - const idx = path.lastIndexOf('/') + 1 |
52 | | - const fileName = path.substring(idx) |
53 | | - const filePath = path.substring(1, idx) |
54 | | - const count = value.length |
55 | | - return ( |
56 | | - <div className='search-item' key={path}> |
57 | | - <div className='search-item-path' onClick={this.handlePathClick}> |
58 | | - <i |
59 | | - className={cx({ |
60 | | - 'fa fa-caret-right': this.state.isFolded, |
61 | | - 'fa fa-caret-down': !this.state.isFolded |
62 | | - })} |
63 | | - /> |
64 | | - <i className={iconStr} /> |
65 | | - {fileName} |
66 | | - <span className='search-item-path-path'>{filePath}</span> |
67 | | - <span className='search-item-count'>{count}</span> |
68 | | - </div> |
| 24 | + handleItemClick = (path, start, end, lineNum) => { |
| 25 | + const selection = new monaco.Selection( |
| 26 | + lineNum, |
| 27 | + start + 1, |
| 28 | + lineNum, |
| 29 | + end + 1, |
| 30 | + ) |
| 31 | + |
| 32 | + openFile({ path, editor: { filePath: path, selection } }) |
| 33 | + } |
69 | 34 |
|
70 | | - {!this.state.isFolded && value.map(line => { |
71 | | - const contentStart = line.content.substring(0, line.indexes[0]) |
72 | | - const contentMiddle = line.content.substring(line.indexes[0], line.indexes[0] + this.props.keyword.length) |
73 | | - const contentEnd = line.content.substring(line.indexes[0] + this.props.keyword.length) |
74 | | - return ( |
75 | | - <div key={`${path}-${line.line}`} className='search-item-line' onClick={() => this.handleItemClick(path, line)}> |
76 | | - <span className='search-item-content'>{contentStart}<h>{contentMiddle}</h>{contentEnd}</span> |
| 35 | + render() { |
| 36 | + const {fileName, pattern, path, results} = this.props; |
| 37 | + const resultSize = results.length; |
| 38 | + const iconStr = 'file-icon ' + (icons.getClassWithColor(path) || 'fa fa-file-text-o'); |
| 39 | + return ( |
| 40 | + <div className='search-item' key={path}> |
| 41 | + <div className='search-item-path' onClick={this.handlePathClick}> |
| 42 | + <i |
| 43 | + className={cx({ |
| 44 | + 'fa fa-caret-right': this.state.isFolded, |
| 45 | + 'fa fa-caret-down': !this.state.isFolded |
| 46 | + })} |
| 47 | + /> |
| 48 | + <i className={iconStr} /> |
| 49 | + {fileName} |
| 50 | + <span className='search-item-path-path'>{path}</span> |
| 51 | + <span className='search-item-count'>{resultSize}</span> |
| 52 | + </div> |
| 53 | + |
| 54 | + { |
| 55 | + !this.state.isFolded && results.map(result => { |
| 56 | + let {start, end, length, innerStart, innerEnd, line, lineNum} = result; |
| 57 | + return ( |
| 58 | + <div key={`${fileName}-${resultSize}-${lineNum}-${start}`} className='search-item-line' onClick={() => this.handleItemClick(path, innerStart, innerEnd, lineNum)}> |
| 59 | + {line && <span className='search-item-content'>{line.substring(0, innerStart)}<b>{line.substring(innerStart, innerEnd)}</b>{line.substring(innerEnd)}</span>} |
| 60 | + </div> |
| 61 | + )}) |
| 62 | + } |
77 | 63 | </div> |
78 | | - ) |
79 | | - })} |
80 | | - </div> |
81 | | - ) |
82 | | - } |
| 64 | + ) |
| 65 | + } |
| 66 | + |
83 | 67 | } |
84 | 68 |
|
85 | 69 | @observer |
86 | 70 | class SearchPanel extends Component { |
87 | | - componentDidMount () { |
88 | | - this.subscribeToFilesearch() |
89 | | - } |
90 | | - onKeyDown = (e) => { |
91 | | - if (e.keyCode === 13) { |
92 | | - this.searchTxt() |
| 71 | + componentDidMount () { |
| 72 | + subscribeToSearch() |
93 | 73 | } |
94 | | - } |
95 | 74 |
|
96 | | - handleKeywordChange = (e) => { |
97 | | - state.keyword = e.target.value |
98 | | - e.stopPropagation() |
99 | | - e.nativeEvent.stopImmediatePropagation() |
100 | | - // this.confirm() |
101 | | - } |
| 75 | + onKeyDown = (e) => { |
| 76 | + if (e.keyCode === 13) { |
| 77 | + this.searchTxt() |
| 78 | + } |
| 79 | + } |
| 80 | + |
| 81 | + handleKeywordChange = (e) => { |
| 82 | + state.searching.pattern = e.target.value |
| 83 | + e.stopPropagation() |
| 84 | + e.nativeEvent.stopImmediatePropagation() |
| 85 | + } |
| 86 | + |
| 87 | + searchTxt = () => { |
| 88 | + if(state.searching.pattern.length == 0 || state.searching.pattern.trim() == '') { |
| 89 | + return ; |
| 90 | + } |
| 91 | + if(state.ws.first) { |
| 92 | + state.ws.first = false |
| 93 | + } |
| 94 | + if(state.searching.isPattern) { |
| 95 | + delegate.searchPattern(state.searching); |
| 96 | + } else { |
| 97 | + delegate.searchString(state.searching); |
| 98 | + } |
| 99 | + } |
| 100 | + |
| 101 | + renderResult () { |
| 102 | + let content = ''; |
| 103 | + if (state.ws.first) { |
| 104 | + content = '' |
| 105 | + } else if (!state.searched.end) { |
| 106 | + content = i18n`panel.left.searching` |
| 107 | + } else if(state.searched.message !== '') { |
| 108 | + content = state.searched.message; |
| 109 | + } else if (state.searched.results.length != 0) { |
| 110 | + const pattern = state.searching.pattern; |
| 111 | + const files = state.searched.results.length |
| 112 | + let number = 0 |
| 113 | + let count = 0 |
| 114 | + |
| 115 | + content = state.searched.results.map((searchChunk) => { |
| 116 | + count++ |
| 117 | + number += searchChunk.results.length |
| 118 | + return (<SearchResultItem |
| 119 | + key={`${pattern}-${count}`} |
| 120 | + fileName={searchChunk.fileName} |
| 121 | + results={searchChunk.results} |
| 122 | + pattern={pattern} |
| 123 | + path={searchChunk.path} |
| 124 | + result={searchChunk.results}/>); |
| 125 | + }) |
| 126 | + return ( |
| 127 | + <div className='search-result-list'> |
| 128 | + <div>{i18n`panel.result.tip${{ files, number }}`}</div> |
| 129 | + {content} |
| 130 | + </div> |
| 131 | + ) |
| 132 | + } else if(state.searched.taskId) { |
| 133 | + content = `${i18n.get('panel.result.blank')}`; |
| 134 | + } |
| 135 | + |
| 136 | + return ( |
| 137 | + <div key={`search-result-list`} className='search-result-list'> |
| 138 | + {content} |
| 139 | + </div> |
| 140 | + ) |
| 141 | + } |
102 | 142 |
|
103 | | - confirm = debounce(() => { |
104 | | - this.searchTxt() |
105 | | - }, 1000) |
| 143 | + render () { |
| 144 | + const { caseSensitive, word, isPattern } = state.searching |
106 | 145 |
|
107 | | - searchTxt = () => { |
108 | | - state.searching = true |
109 | | - api.searchTxt(state.keyword).then((data) => { |
110 | | - state.taskId = data.taskId |
111 | | - }).catch((res) => { |
112 | | - notify({ message: `Search failed: ${res.msg}`, notifyType: NOTIFY_TYPE.ERROR }) |
113 | | - }) |
114 | | - } |
115 | | - subscribeToFilesearch = () => { |
116 | | - autorun(() => { |
117 | | - if (!config.fsSocketConnected) return |
118 | | - const client = FsSocketClient.$$singleton.stompClient |
119 | | - client.subscribe(`/topic/ws/${config.spaceKey}/txt/search`, (frame) => { |
120 | | - const data = JSON.parse(frame.body) |
121 | | - this.setDate(data) |
122 | | - }) |
123 | | - }) |
124 | | - } |
| 146 | + return ( |
| 147 | + <div className='search-panel'> |
| 148 | + <div className='search-panel-title'> |
| 149 | + {i18n`panel.left.find`} |
| 150 | + </div> |
| 151 | + <div className='search-panel-input'> |
| 152 | + <div className='search-controls'> |
| 153 | + <input type='text' |
| 154 | + className='form-control' |
| 155 | + value={state.keyword} |
| 156 | + onChange={this.handleKeywordChange} |
| 157 | + onKeyDown={this.onKeyDown} |
| 158 | + placeholder={i18n.get(`panel.left.placeholder`)} |
| 159 | + /> |
| 160 | + </div> |
| 161 | + <div className='search-checkbox'> |
| 162 | + <span title={i18n.get('panel.checkbox.case')} |
| 163 | + onClick={() => this.caseSensitive(caseSensitive)} |
| 164 | + className={caseSensitive ? 'active' : ''}>{'Aa'}</span> |
| 165 | + <span title={i18n.get('panel.checkbox.word')} |
| 166 | + onClick={() => this.word(word)} |
| 167 | + className={word ? 'active' : ''}>{'Al'}</span> |
| 168 | + <span title={i18n.get('panel.checkbox.pattern')} |
| 169 | + onClick={() => this.pattern(isPattern)} |
| 170 | + className={isPattern ? 'active' : ''}>{'.*'}</span> |
| 171 | + </div> |
| 172 | + </div> |
| 173 | + {this.renderResult()} |
| 174 | + </div> |
| 175 | + ) |
| 176 | + } |
125 | 177 |
|
126 | | - setDate = debounce((data) => { |
127 | | - if (data.taskId === state.taskId) { |
128 | | - state.result = data |
129 | | - state.searching = false |
| 178 | + caseSensitive = caseSensitive => { |
| 179 | + state.searching.caseSensitive = !caseSensitive |
130 | 180 | } |
131 | | - }, 500) |
132 | 181 |
|
133 | | - renderResult () { |
134 | | - let content = '' |
135 | | - if (state.searching) { |
136 | | - content = 'Searching...' |
137 | | - } else if (state.result.results) { |
138 | | - const keyword = state.result.keyword |
139 | | - content = Object.entries(state.result.results).map(([key, value]) => { |
140 | | - if (key.startsWith('/.git/')) return null |
141 | | - return (<SearchResultItem keyword={keyword} path={key} key={key} value={value} />) |
142 | | - }) |
| 182 | + word = word => { |
| 183 | + state.searching.word = !word |
| 184 | + if (!word) { |
| 185 | + state.searching.isPattern = false |
| 186 | + } |
143 | 187 | } |
144 | 188 |
|
145 | | - return ( |
146 | | - <div className='search-result-list'> |
147 | | - {content} |
148 | | - </div> |
149 | | - ) |
150 | | - } |
| 189 | + pattern = isPattern => { |
| 190 | + state.searching.isPattern = !isPattern |
| 191 | + if (!isPattern) { |
| 192 | + state.searching.word = false |
| 193 | + } |
| 194 | + } |
151 | 195 |
|
152 | | - render () { |
153 | | - return ( |
154 | | - <div className='search-panel'> |
155 | | - <div className='search-panel-title'> |
156 | | - {i18n`panel.left.find`} |
157 | | - </div> |
158 | | - <div className='search-panel-input'> |
159 | | - <input type='text' |
160 | | - className='form-control' |
161 | | - value={state.keyword} |
162 | | - onChange={this.handleKeywordChange} |
163 | | - onKeyDown={this.onKeyDown} |
164 | | - placeholder={i18n.get('panel.left.find')} |
165 | | - /> |
166 | | - </div> |
167 | | - {this.renderResult()} |
168 | | - </div> |
169 | | - ) |
170 | | - } |
171 | 196 | } |
172 | 197 |
|
173 | 198 | export default SearchPanel |
0 commit comments