Overhaul the query history tab to allow browsing of the history and full query text. Fixes #2282

Patch by Joao and the team at Pivotal.
This commit is contained in:
Joao Pedro De Almeida Pereira 2017-06-27 10:55:57 -04:00 committed by Dave Page
parent e413186d23
commit 7f55412059
63 changed files with 10645 additions and 8977 deletions

View File

@ -38,6 +38,7 @@
"moment": "^2.18.1", "moment": "^2.18.1",
"react": "file:../web/pgadmin/static/vendor/react", "react": "file:../web/pgadmin/static/vendor/react",
"react-dom": "file:../web/pgadmin/static/vendor/react-dom", "react-dom": "file:../web/pgadmin/static/vendor/react-dom",
"react-split-pane": "^0.1.63",
"requirejs": "~2.3.3", "requirejs": "~2.3.3",
"slickgrid": "git+https://github.com/6pac/SlickGrid.git#2.3.7", "slickgrid": "git+https://github.com/6pac/SlickGrid.git#2.3.7",
"underscore": "~1.8.3", "underscore": "~1.8.3",

View File

@ -75,10 +75,18 @@ class QueryToolJourneyTest(BaseFeatureTest):
self._execute_query("SELECT * FROM shoes") self._execute_query("SELECT * FROM shoes")
self.page.click_tab("History") self.page.click_tab("History")
history_element = self.page.find_by_id("history_grid") history_element = self.page.find_by_id("query_list")
self.assertIn("SELECT * FROM test_table", history_element.text) self.assertIn("SELECT * FROM test_table", history_element.text)
self.assertIn("SELECT * FROM shoes", history_element.text) self.assertIn("SELECT * FROM shoes", history_element.text)
history_detail = self.page.find_by_id("query_detail")
self.assertIn("SELECT * FROM shoes", history_detail.text)
old_history_list_element = self.page.find_by_xpath("//*[@id='query_list']/ul/li[2]")
self.page.click_element(old_history_list_element)
history_detail = self.page.find_by_id("query_detail")
self.assertIn("SELECT * FROM test_table", history_detail.text)
def __clear_query_tool(self): def __clear_query_tool(self):
self.page.click_element(self.page.find_by_xpath("//*[@id='btn-edit']")) self.page.click_element(self.page.find_by_xpath("//*[@id='btn-edit']"))
self.page.click_modal('Yes') self.page.click_modal('Yes')

View File

@ -55,12 +55,6 @@ iframe {
border-width: 0; border-width: 0;
} }
/* Ensure the codemirror editor displays full height gutters when resized */
.CodeMirror, .CodeMirror-gutter {
height: 100% !important;
min-height: 100% !important;
}
/* Padding for the treeview */ /* Padding for the treeview */
.browser-browser-pane { .browser-browser-pane {
padding-left: 0; padding-left: 0;
@ -926,22 +920,6 @@ td.edit-cell.editable.sortable.renderable.editor {
padding: 0px; padding: 0px;
} }
/*
* Remove border right from "CodeMirror-gutters"
* class becuase its height doesn't increase
* with every new line added, instead add border
* right to ".CodeMirror-linenumber" class which
* adds a new div for every new line added
*/
.sql_field_layout .CodeMirror-gutters {
border-right: none;
}
.sql_field_layout .CodeMirror-linenumber {
border-right: 1px solid #ddd;
background-color: #f7f7f7;
}
.tab-pane.SQL { .tab-pane.SQL {
padding: 0px 7px 0px 0px; padding: 0px 7px 0px 0px;
height: 100%; height: 100%;
@ -1420,22 +1398,6 @@ table.backgrid {
background: none; background: none;
} }
/* class to disable Codemirror editor */
.cm_disabled {
background: #EEEEEE;
}
/* make syntax-highlighting bold */
.cm-s-default .cm-keyword {
font-weight: 600;
color: #908;
}
.cm-s-default .cm-number {
font-weight: 600;
color: #964;
}
.pgadmin-controls.sql_field_layout { .pgadmin-controls.sql_field_layout {
margin: 0; margin: 0;
padding: 0; padding: 0;
@ -1443,10 +1405,6 @@ table.backgrid {
z-index: 1; z-index: 1;
} }
.pgadmin-controls.sql_field_layout .CodeMirror {
line-height: 22px;
}
.pgadmin-controls.focused { .pgadmin-controls.focused {
border-color: #66afe9; border-color: #66afe9;
outline: 0; outline: 0;
@ -1502,22 +1460,6 @@ body {
user-select: text; user-select: text;
} }
/* Codemirror buttons */
.CodeMirror-dialog button {
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
color: white;
font-size: 70%;
background-image: -webkit-linear-gradient(top, #337ab7 0%, #265a88 100%);
background-image: -o-linear-gradient(top, #337ab7 0%, #265a88 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#265a88));
background-image: linear-gradient(to bottom, #337ab7 0%, #265a88 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff265a88', GradientType=0);
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
background-repeat: repeat-x;
border-color: #245580;
border-radius: 4px;
}
.dashboard-tab-container table, .dashboard-tab-container table,
.pg-panel-statistics-container table, .pg-panel-statistics-container table,
.pg-panel-depends-container table, .pg-panel-depends-container table,

View File

@ -3,3 +3,69 @@
color: #333333 !important; color: #333333 !important;
background-color: #e8e8e8 !important; background-color: #e8e8e8 !important;
} }
/* Ensure the codemirror editor displays full height gutters when resized */
.CodeMirror, .CodeMirror-gutter {
height: 100% !important;
min-height: 100% !important;
}
/*
* Remove border right from "CodeMirror-gutters"
* class becuase its height doesn't increase
* with every new line added, instead add border
* right to ".CodeMirror-linenumber" class which
* adds a new div for every new line added
*/
.sql_field_layout .CodeMirror-gutters {
border-right: none;
}
.sql_field_layout .CodeMirror-linenumber {
border-right: 1px solid #ddd;
background-color: #f7f7f7;
}
/* class to disable Codemirror editor */
.cm_disabled {
background: #EEEEEE;
}
/* make syntax-highlighting bold */
.cm-s-default .cm-keyword {
font-weight: 600;
color: #908;
}
.cm-s-default .cm-number {
font-weight: 600;
color: #964;
}
.pgadmin-controls.sql_field_layout .CodeMirror {
line-height: 22px;
}
/* Codemirror buttons */
.CodeMirror-dialog button {
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
color: white;
font-size: 70%;
background-image: -webkit-linear-gradient(top, #337ab7 0%, #265a88 100%);
background-image: -o-linear-gradient(top, #337ab7 0%, #265a88 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#265a88));
background-image: linear-gradient(to bottom, #337ab7 0%, #265a88 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff265a88', GradientType=0);
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
background-repeat: repeat-x;
border-color: #245580;
border-radius: 4px;
}
#history-detail-query .CodeMirror {
border: 1px solid #CCCCCC;
background-color: #F7F7F7;
width: 100%;
padding-left: 5px;
position: absolute;
}

View File

@ -781,3 +781,35 @@ lgg-el-container[el=md] .pg-el-lg-8,
.user-language select{ .user-language select{
height: 25px !important; height: 25px !important;
} }
.Resizer {
background: #dddddd;
z-index: 1;
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
box-sizing: border-box;
-moz-background-clip: padding;
-webkit-background-clip: padding;
background-clip: padding-box;
}
.Resizer.horizontal {
width: 100%;
height: 4px;
cursor: ew-resize;
border-bottom: 1px solid #aaaaaa;
}
.Resizer.vertical {
width: 4px;
cursor: ew-resize;
border-right: 1px solid #aaaaaa;
}
.Resizer.disabled {
cursor: not-allowed;
}
.Resizer.disabled:hover {
border-color: transparent;
}

View File

@ -7,8 +7,8 @@
// //
////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////
import historyCollection from './history_collection'; import HistoryCollection from './history_collection';
export { export {
historyCollection, HistoryCollection,
}; };

View File

@ -0,0 +1,61 @@
//////////////////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2017, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////////////////
import React from 'react';
import $ from '../../../vendor/jquery/jquery-1.11.2';
import code_mirror from '../../../vendor/codemirror/lib/codemirror';
export default class CodeMirror extends React.Component {
constructor(props) {
super(props);
this.state = {
shouldHydrate: true,
};
}
componentDidMount() {
this.editor = code_mirror(this.container);
this.hydrateInterval = setInterval(this.hydrateWhenBecomesVisible.bind(this), 100);
this.hydrate(this.props);
}
componentWillUnmount() {
clearInterval(this.hydrateInterval);
}
componentWillReceiveProps(nextProps) {
this.hydrate(nextProps);
}
hydrateWhenBecomesVisible() {
const isVisible = $(this.container).is(':visible');
if (isVisible && this.state.shouldHydrate) {
this.hydrate(this.props);
this.setState({shouldHydrate: false});
} else if (!isVisible) {
this.setState({shouldHydrate: true});
}
}
hydrate(props) {
Object.keys(props.options || {}).forEach(key => this.editor.setOption(key, props.options[key]));
this.editor.setValue(props.value || '');
this.editor.refresh();
}
render() {
return (
<div ref={(self) => this.container = self}/>
);
}
}

View File

@ -0,0 +1,60 @@
//////////////////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2017, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////////////////
import React from 'react';
import Shapes from '../../react_shapes';
import NonSelectableElementStyle from '../../styles/non_selectable';
import MessageHeaderStyle from '../../styles/header_label';
const containerStyle = {
flex: '2 2 0%',
flexDirection: 'column',
display: 'flex',
};
const messageContainerStyle = {
flex: '0 1 auto',
overflow: 'auto',
position: 'relative',
height: '100%',
};
const errorMessageStyle = {
border: '0',
paddingLeft: '0',
backgroundColor: '#ffffff',
fontSize: '13px',
position: 'absolute',
};
const messageLabelStyle = _.extend({flex: '0 0 auto'},
MessageHeaderStyle,
NonSelectableElementStyle);
export default class HistoryDetailMessage extends React.Component {
render() {
return (
<div style={containerStyle}>
<div style={messageLabelStyle}>
Messages
</div>
<div style={messageContainerStyle}>
<pre style={errorMessageStyle}>
{this.props.historyEntry.message}
</pre>
</div>
</div>);
}
}
HistoryDetailMessage.propTypes = {
historyEntry: Shapes.historyDetail,
};

View File

@ -0,0 +1,54 @@
//////////////////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2017, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////////////////
import React from 'react';
import moment from 'moment';
import Shapes from '../../react_shapes';
import HeaderDescriptionStyle from '../../styles/header_label';
const queryMetaDataStyle = {
flex: 1,
};
const headerStyle = {
display: 'flex',
};
const headerValueStyle = {
display: 'block',
fontSize: '14px',
};
export default class HistoryDetailMetadata extends React.Component {
formatDate(date) {
return (moment(date).format('M-D-YY HH:mm:ss'));
}
queryMetaData(data, description) {
return <div style={queryMetaDataStyle}>
<span style={headerValueStyle}>
{data}
</span>
<span style={HeaderDescriptionStyle}>
{description}
</span>
</div>;
}
render() {
return <div style={headerStyle}>
{this.queryMetaData(this.formatDate(this.props.historyEntry.start_time), 'Date')}
{this.queryMetaData(this.props.historyEntry.row_affected.toLocaleString(), 'Rows Affected')}
{this.queryMetaData(this.props.historyEntry.total_time, 'Duration')}
</div>;
}
}
HistoryDetailMetadata.propTypes = {
historyEntry: Shapes.historyDetail,
};

View File

@ -0,0 +1,33 @@
//////////////////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2017, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////////////////
import React from 'react';
import '../../../vendor/codemirror/mode/sql/sql';
import CodeMirror from './code_mirror';
import Shapes from '../../react_shapes';
export default class HistoryDetailQuery extends React.Component {
render() {
return (
<div id="history-detail-query">
<CodeMirror
value={this.props.historyEntry.query}
options={{
mode: 'text/x-pgsql',
readOnly: true,
}}
/>
</div>);
}
}
HistoryDetailQuery.propTypes = {
historyEntry: Shapes.historyDetail,
};

View File

@ -0,0 +1,20 @@
/////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2017, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////
import QueryHistoryVanillaEntry from './query_history_vanilla_entry';
import update from 'immutability-helper';
import {errorStyle} from '../../styles/history_entry_styles';
export default class QueryHistoryErrorEntry extends QueryHistoryVanillaEntry {
componentWillMount() {
this.setState({
outerDivStyle: update(this.state.outerDivStyle, {$merge: errorStyle}),
});
}
}

View File

@ -0,0 +1,21 @@
/////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2017, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////
import QueryHistoryVanillaEntry from './query_history_vanilla_entry';
import update from 'immutability-helper';
import {selectedFontStyle, selectedOuterStyle} from '../../styles/history_entry_styles';
export default class QueryHistorySelectedEntry extends QueryHistoryVanillaEntry {
componentWillMount() {
this.setState({
outerDivStyle: update(this.state.outerDivStyle, {$merge: selectedOuterStyle}),
secondLineStyle: update(this.state.secondLineStyle, {$merge: selectedFontStyle}),
});
}
}

View File

@ -0,0 +1,22 @@
/////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2017, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////
import QueryHistoryVanillaEntry from './query_history_vanilla_entry';
import update from 'immutability-helper';
import {selectedFontStyle, selectedOuterStyle, selectedErrorBgColor} from '../../styles/history_entry_styles';
export default class QueryHistorySelectedErrorEntry extends QueryHistoryVanillaEntry {
componentWillMount() {
let selectedErrorStyle = update(selectedOuterStyle, {$merge: selectedErrorBgColor});
this.setState({
outerDivStyle: update(this.state.outerDivStyle, {$merge: selectedErrorStyle}),
secondLineStyle: update(this.state.secondLineStyle, {$merge: selectedFontStyle}),
});
}
}

View File

@ -0,0 +1,47 @@
/////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2017, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////
import React from 'react';
import moment from 'moment';
import Shapes from '../../react_shapes';
import {plainOuterDivStyle, plainSecondLineStyle, sqlStyle, timestampStyle} from '../../styles/history_entry_styles';
export default class QueryHistoryVanillaEntry extends React.Component {
formatDate(date) {
return (moment(date).format('MMM D YYYY [] HH:mm:ss'));
}
constructor(props) {
super(props);
this.state = {
outerDivStyle: plainOuterDivStyle,
secondLineStyle: plainSecondLineStyle,
};
}
render() {
return (
<div style={this.state.outerDivStyle}>
<div style={sqlStyle}>
{this.props.historyEntry.query}
</div>
<div style={this.state.secondLineStyle}>
<div style={timestampStyle}>
{this.formatDate(this.props.historyEntry.start_time)}
</div>
</div>
</div>
);
}
}
QueryHistoryVanillaEntry.propTypes = {
historyEntry: Shapes.historyDetail,
isSelected: React.PropTypes.bool,
};

View File

@ -8,8 +8,17 @@
////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////
import React from 'react'; import React from 'react';
import SplitPane from 'react-split-pane';
import QueryHistoryEntry from './query_history_entry'; import QueryHistoryEntry from './query_history_entry';
import QueryHistoryDetail from './query_history_detail';
import Shapes from '../react_shapes';
const queryEntryListDivStyle = {
overflowY: 'auto',
};
const queryDetailDivStyle = {
display: 'flex',
};
const liStyle = { const liStyle = {
borderBottom: '1px solid #cccccc', borderBottom: '1px solid #cccccc',
}; };
@ -21,29 +30,67 @@ export default class QueryHistory extends React.Component {
this.state = { this.state = {
history: [], history: [],
selectedEntry: 0,
}; };
} }
componentWillMount() { componentWillMount() {
this.setState({history: this.props.historyCollection.historyList}); this.resetCurrentHistoryDetail(this.props.historyCollection.historyList);
this.props.historyCollection.onChange((historyList) => this.setState({history: historyList})); this.props.historyCollection.onChange((historyList) => {
this.resetCurrentHistoryDetail(historyList);
});
}
componentDidMount() {
this.resetCurrentHistoryDetail(this.state.history);
}
getCurrentHistoryDetail() {
return this.state.currentHistoryDetail;
}
setCurrentHistoryDetail(index, historyList) {
this.setState({
history: historyList,
currentHistoryDetail: this.retrieveOrderedHistory().value()[index],
selectedEntry: index,
});
}
resetCurrentHistoryDetail(historyList) {
this.setCurrentHistoryDetail(0, historyList);
}
retrieveOrderedHistory() {
return _.chain(this.state.history)
.sortBy(historyEntry => historyEntry.start_time)
.reverse();
}
onClickHandler(index) {
this.setCurrentHistoryDetail(index, this.state.history);
} }
render() { render() {
return <ul> return (
{_.chain(this.state.history) <SplitPane defaultSize="50%" split="vertical" pane1Style={queryEntryListDivStyle}
.sortBy(historyEntry => historyEntry.start_time) pane2Style={queryDetailDivStyle}>
.reverse() <div id='query_list'>
.map((entry, index) => <ul>
<li key={index} style={liStyle}> {this.retrieveOrderedHistory()
<QueryHistoryEntry historyEntry={entry}/> .map((entry, index) =>
</li>) <li key={index} style={liStyle} onClick={this.onClickHandler.bind(this, index)}>
.value() <QueryHistoryEntry historyEntry={entry} isSelected={index == this.state.selectedEntry}/>
} </li>)
</ul>; .value()
}
</ul>
</div>
<QueryHistoryDetail historyEntry={this.getCurrentHistoryDetail()}/>
</SplitPane>);
} }
} }
QueryHistory.propTypes = { QueryHistory.propTypes = {
historyCollection: React.PropTypes.object.isRequired, historyCollection: Shapes.historyCollectionClass.isRequired,
}; };

View File

@ -0,0 +1,75 @@
//////////////////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2017, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////////////////
import React from 'react';
import HistoryDetailMetadata from './detail/history_detail_metadata';
import HistoryDetailQuery from './detail/history_detail_query';
import HistoryDetailMessage from './detail/history_detail_message';
import Shapes from '../react_shapes';
const outerStyle = {
width: '100%',
paddingTop: '10px',
display: 'flex',
flexDirection: 'column',
};
const detailVerticalTop = {
flex: 1,
padding: '0 10px',
};
const detailVerticalMiddle = {
flex: 5,
marginLeft: '10px',
marginRight: '10px',
height: 0,
position: 'relative',
};
const hrStyle = {
borderColor: '#cccccc',
marginTop: '11px',
marginBottom: '8px',
};
const detailVerticalBottom = {
flex: 2,
display: 'flex',
paddingLeft: '10px',
};
export default class QueryHistoryDetail extends React.Component {
render() {
if (!_.isUndefined(this.props.historyEntry)) {
return (
<div id='query_detail' style={outerStyle}>
<div style={detailVerticalTop}>
<HistoryDetailMetadata {...this.props} />
</div>
<div style={detailVerticalMiddle}>
<HistoryDetailQuery {...this.props}/>
</div>
<div>
<hr style={hrStyle}/>
</div>
<div style={detailVerticalBottom}>
<HistoryDetailMessage {...this.props}/>
</div>
</div>);
} else {
return <p></p>;
}
}
}
QueryHistoryDetail.propTypes = {
historyEntry: Shapes.historyDetail,
};

View File

@ -1,93 +1,42 @@
///////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////
// //
// pgAdmin 4 - PostgreSQL Tools // pgAdmin 4 - PostgreSQL Tools
// //
// Copyright (C) 2013 - 2017, The pgAdmin Development Team // Copyright (C) 2013 - 2017, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence // This software is released under the PostgreSQL Licence
// //
////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////
import React from 'react'; import React from 'react';
import update from 'immutability-helper'; import Shapes from '../react_shapes';
import moment from 'moment'; import QueryHistoryErrorEntry from './entry/query_history_error_entry';
import QueryHistorySelectedErrorEntry from './entry/query_history_selected_error_entry';
const outerDivStyle = { import QueryHistorySelectedEntry from './entry/query_history_selected_entry';
paddingLeft: '10px', import QueryHistoryVanillaEntry from './entry/query_history_vanilla_entry';
fontFamily: 'monospace',
paddingRight: '20px',
fontSize: '14px',
backgroundColor: '#FFF',
};
const sqlStyle = {
textOverflow: 'ellipsis',
overflow: 'hidden',
whiteSpace: 'nowrap',
userSelect: 'auto',
};
const secondLineStyle = {
display: 'flex',
flexDirection: 'row',
justifyContent: 'space-between',
fontSize: '13px',
color: '#888888',
};
const timestampStyle = {
alignSelf: 'flex-start',
};
const rowsAffectedStyle = {
alignSelf: 'flex-end',
};
const errorMessageStyle = {
textOverflow: 'ellipsis',
overflow: 'hidden',
whiteSpace: 'nowrap',
userSelect: 'auto',
fontSize: '13px',
color: '#888888',
};
export default class QueryHistoryEntry extends React.Component { export default class QueryHistoryEntry extends React.Component {
formatDate(date) {
return (moment(date).format('MMM D YYYY [] HH:mm:ss'));
}
render() { render() {
return ( if (this.hasError()) {
<div style={this.queryEntryBackgroundColor()}> if (this.props.isSelected) {
<div style={sqlStyle}> return <QueryHistorySelectedErrorEntry {...this.props}/>;
{this.props.historyEntry.query} } else {
</div> return <QueryHistoryErrorEntry {...this.props}/>;
<div style={secondLineStyle}> }
<div style={timestampStyle}> } else {
{this.formatDate(this.props.historyEntry.start_time)} / if (this.props.isSelected) {
total time: {this.props.historyEntry.total_time} return <QueryHistorySelectedEntry {...this.props}/>;
</div> } else {
<div style={rowsAffectedStyle}> return <QueryHistoryVanillaEntry {...this.props}/>;
{this.props.historyEntry.row_affected} rows affected }
</div> }
</div>
<div style={errorMessageStyle}>
{this.props.historyEntry.message}
</div>
</div>
);
} }
queryEntryBackgroundColor() { hasError() {
if (!this.props.historyEntry.status) { return !this.props.historyEntry.status;
return update(outerDivStyle, {$merge: {backgroundColor: '#F7D0D5'}});
}
return outerDivStyle;
} }
} }
QueryHistoryEntry.propTypes = { QueryHistoryEntry.propTypes = {
historyEntry: React.PropTypes.shape({ historyEntry: Shapes.historyDetail,
query: React.PropTypes.string, isSelected: React.PropTypes.bool,
start_time: React.PropTypes.instanceOf(Date),
status: React.PropTypes.bool,
total_time: React.PropTypes.string,
row_affected: React.PropTypes.int,
message: React.PropTypes.string,
}),
}; };

View File

@ -0,0 +1,31 @@
//////////////////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2017, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////////////////
import React from 'react';
let historyDetail =
React.PropTypes.shape({
query: React.PropTypes.string,
start_time: React.PropTypes.instanceOf(Date),
status: React.PropTypes.bool,
total_time: React.PropTypes.string,
row_affected: React.PropTypes.int,
message: React.PropTypes.string,
});
let historyCollectionClass =
React.PropTypes.shape({
historyList: React.PropTypes.array.isRequired,
onChange: React.PropTypes.func.isRequired,
});
export default {
historyDetail,
historyCollectionClass,
};

View File

@ -0,0 +1,14 @@
//////////////////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2017, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////////////////
export default {
display: 'block',
fontSize: '12px',
color: '#888888',
};

View File

@ -0,0 +1,57 @@
//////////////////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2017, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////////////////
import update from 'immutability-helper';
export const plainOuterDivStyle = {
paddingLeft: '8px',
paddingRight: '18px',
paddingTop: '-2px',
paddingBottom: '-2px',
fontFamily: 'monospace',
fontSize: '14px',
backgroundColor: '#FFF',
border: '2px solid transparent',
marginLeft: '1px',
};
export const sqlStyle = {
textOverflow: 'ellipsis',
overflow: 'hidden',
whiteSpace: 'nowrap',
userSelect: 'auto',
};
export const plainSecondLineStyle = {
display: 'flex',
flexDirection: 'row',
justifyContent: 'space-between',
fontSize: '13px',
color: '#888888',
};
export const timestampStyle = {
alignSelf: 'flex-start',
};
export const selectedFontStyle = {
color: '#2c76b4',
fontWeight: 'bold',
};
export const selectedOuterStyle = update(selectedFontStyle, {
$merge: {
border: '2px solid #2c76b4',
backgroundColor: '#e7f2ff',
},
});
export const errorStyle = {backgroundColor: '#F7D0D5'};
export const selectedErrorBgColor = {backgroundColor: '#DCC4D1'};

View File

@ -0,0 +1,18 @@
//////////////////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2017, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////////////////
export default {
WebkitTouchCallout: 'none',
WebkitUserSelect: 'none',
KhtmlUserSelect: 'none',
MozUserSelect: 'none',
msUserSelect: 'none',
userSelect: 'none',
cursor: 'default',
};

View File

@ -46,12 +46,17 @@
// Rough heuristic to try and detect lines that are part of multi-line string // Rough heuristic to try and detect lines that are part of multi-line string
function probablyInsideString(cm, pos, line) { function probablyInsideString(cm, pos, line) {
return /\bstring\b/.test(cm.getTokenTypeAt(Pos(pos.line, 0))) && !/^[\'\"`]/.test(line) return /\bstring\b/.test(cm.getTokenTypeAt(Pos(pos.line, 0))) && !/^[\'\"\`]/.test(line)
}
function getMode(cm, pos) {
var mode = cm.getMode()
return mode.useInnerComments === false || !mode.innerMode ? mode : cm.getModeAt(pos)
} }
CodeMirror.defineExtension("lineComment", function(from, to, options) { CodeMirror.defineExtension("lineComment", function(from, to, options) {
if (!options) options = noOptions; if (!options) options = noOptions;
var self = this, mode = self.getModeAt(from); var self = this, mode = getMode(self, from);
var firstLine = self.getLine(from.line); var firstLine = self.getLine(from.line);
if (firstLine == null || probablyInsideString(self, from, firstLine)) return; if (firstLine == null || probablyInsideString(self, from, firstLine)) return;
@ -95,7 +100,7 @@
CodeMirror.defineExtension("blockComment", function(from, to, options) { CodeMirror.defineExtension("blockComment", function(from, to, options) {
if (!options) options = noOptions; if (!options) options = noOptions;
var self = this, mode = self.getModeAt(from); var self = this, mode = getMode(self, from);
var startString = options.blockCommentStart || mode.blockCommentStart; var startString = options.blockCommentStart || mode.blockCommentStart;
var endString = options.blockCommentEnd || mode.blockCommentEnd; var endString = options.blockCommentEnd || mode.blockCommentEnd;
if (!startString || !endString) { if (!startString || !endString) {
@ -103,6 +108,7 @@
self.lineComment(from, to, options); self.lineComment(from, to, options);
return; return;
} }
if (/\bcomment\b/.test(self.getTokenTypeAt(Pos(from.line, 0)))) return
var end = Math.min(to.line, self.lastLine()); var end = Math.min(to.line, self.lastLine());
if (end != from.line && to.ch == 0 && nonWS.test(self.getLine(end))) --end; if (end != from.line && to.ch == 0 && nonWS.test(self.getLine(end))) --end;
@ -128,7 +134,7 @@
CodeMirror.defineExtension("uncomment", function(from, to, options) { CodeMirror.defineExtension("uncomment", function(from, to, options) {
if (!options) options = noOptions; if (!options) options = noOptions;
var self = this, mode = self.getModeAt(from); var self = this, mode = getMode(self, from);
var end = Math.min(to.ch != 0 || to.line == from.line ? to.line : to.line - 1, self.lastLine()), start = Math.min(from.line, end); var end = Math.min(to.ch != 0 || to.line == from.line ? to.line : to.line - 1, self.lastLine()), start = Math.min(from.line, end);
// Try finding line comments // Try finding line comments
@ -140,7 +146,7 @@
var line = self.getLine(i); var line = self.getLine(i);
var found = line.indexOf(lineString); var found = line.indexOf(lineString);
if (found > -1 && !/comment/.test(self.getTokenTypeAt(Pos(i, found + 1)))) found = -1; if (found > -1 && !/comment/.test(self.getTokenTypeAt(Pos(i, found + 1)))) found = -1;
if (found == -1 && (i != end || i == start) && nonWS.test(line)) break lineComment; if (found == -1 && nonWS.test(line)) break lineComment;
if (found > -1 && nonWS.test(line.slice(0, found))) break lineComment; if (found > -1 && nonWS.test(line.slice(0, found))) break lineComment;
lines.push(line); lines.push(line);
} }
@ -162,15 +168,19 @@
var endString = options.blockCommentEnd || mode.blockCommentEnd; var endString = options.blockCommentEnd || mode.blockCommentEnd;
if (!startString || !endString) return false; if (!startString || !endString) return false;
var lead = options.blockCommentLead || mode.blockCommentLead; var lead = options.blockCommentLead || mode.blockCommentLead;
var startLine = self.getLine(start), endLine = end == start ? startLine : self.getLine(end); var startLine = self.getLine(start), open = startLine.indexOf(startString)
var open = startLine.indexOf(startString), close = endLine.lastIndexOf(endString); if (open == -1) return false
var endLine = end == start ? startLine : self.getLine(end)
var close = endLine.indexOf(endString, end == start ? open + startString.length : 0);
if (close == -1 && start != end) { if (close == -1 && start != end) {
endLine = self.getLine(--end); endLine = self.getLine(--end);
close = endLine.lastIndexOf(endString); close = endLine.indexOf(endString);
} }
if (open == -1 || close == -1 || var insideStart = Pos(start, open + 1), insideEnd = Pos(end, close + 1)
!/comment/.test(self.getTokenTypeAt(Pos(start, open + 1))) || if (close == -1 ||
!/comment/.test(self.getTokenTypeAt(Pos(end, close + 1)))) !/comment/.test(self.getTokenTypeAt(insideStart)) ||
!/comment/.test(self.getTokenTypeAt(insideEnd)) ||
self.getRange(insideStart, insideEnd, "\n").indexOf(endString) > -1)
return false; return false;
// Avoid killing block comments completely outside the selection. // Avoid killing block comments completely outside the selection.

View File

@ -38,6 +38,9 @@
var height = (options && options.height) || node.offsetHeight; var height = (options && options.height) || node.offsetHeight;
this._setSize(null, info.heightLeft -= height); this._setSize(null, info.heightLeft -= height);
info.panels++; info.panels++;
if (options.stable && isAtTop(this, node))
this.scrollTo(null, this.getScrollInfo().top + height)
return new Panel(this, node, options, height); return new Panel(this, node, options, height);
}); });
@ -54,6 +57,8 @@
this.cleared = true; this.cleared = true;
var info = this.cm.state.panels; var info = this.cm.state.panels;
this.cm._setSize(null, info.heightLeft += this.height); this.cm._setSize(null, info.heightLeft += this.height);
if (this.options.stable && isAtTop(this.cm, this.node))
this.cm.scrollTo(null, this.cm.getScrollInfo().top - this.height)
info.wrapper.removeChild(this.node); info.wrapper.removeChild(this.node);
if (--info.panels == 0) removePanels(this.cm); if (--info.panels == 0) removePanels(this.cm);
}; };
@ -61,7 +66,7 @@
Panel.prototype.changed = function(height) { Panel.prototype.changed = function(height) {
var newHeight = height == null ? this.node.offsetHeight : height; var newHeight = height == null ? this.node.offsetHeight : height;
var info = this.cm.state.panels; var info = this.cm.state.panels;
this.cm._setSize(null, info.height += (newHeight - this.height)); this.cm._setSize(null, info.heightLeft -= (newHeight - this.height));
this.height = newHeight; this.height = newHeight;
}; };
@ -109,4 +114,10 @@
cm.setSize = cm._setSize; cm.setSize = cm._setSize;
cm.setSize(); cm.setSize();
} }
function isAtTop(cm, dom) {
for (var sibling = dom.nextSibling; sibling; sibling = sibling.nextSibling)
if (sibling == cm.getWrapperElement()) return true
return false
}
}); });

View File

@ -11,30 +11,26 @@
})(function(CodeMirror) { })(function(CodeMirror) {
"use strict"; "use strict";
CodeMirror.defineOption("rulers", false, function(cm, val, old) { CodeMirror.defineOption("rulers", false, function(cm, val) {
if (old && old != CodeMirror.Init) { if (cm.state.rulerDiv) {
clearRulers(cm); cm.state.rulerDiv.parentElement.removeChild(cm.state.rulerDiv)
cm.off("refresh", refreshRulers); cm.state.rulerDiv = null
cm.off("refresh", drawRulers)
} }
if (val && val.length) { if (val && val.length) {
setRulers(cm); cm.state.rulerDiv = cm.display.lineSpace.parentElement.insertBefore(document.createElement("div"), cm.display.lineSpace)
cm.on("refresh", refreshRulers); cm.state.rulerDiv.className = "CodeMirror-rulers"
drawRulers(cm)
cm.on("refresh", drawRulers)
} }
}); });
function clearRulers(cm) { function drawRulers(cm) {
for (var i = cm.display.lineSpace.childNodes.length - 1; i >= 0; i--) { cm.state.rulerDiv.textContent = ""
var node = cm.display.lineSpace.childNodes[i];
if (/(^|\s)CodeMirror-ruler($|\s)/.test(node.className))
node.parentNode.removeChild(node);
}
}
function setRulers(cm) {
var val = cm.getOption("rulers"); var val = cm.getOption("rulers");
var cw = cm.defaultCharWidth(); var cw = cm.defaultCharWidth();
var left = cm.charCoords(CodeMirror.Pos(cm.firstLine(), 0), "div").left; var left = cm.charCoords(CodeMirror.Pos(cm.firstLine(), 0), "div").left;
var minH = cm.display.scroller.offsetHeight + 30; cm.state.rulerDiv.style.minHeight = (cm.display.scroller.offsetHeight + 30) + "px";
for (var i = 0; i < val.length; i++) { for (var i = 0; i < val.length; i++) {
var elt = document.createElement("div"); var elt = document.createElement("div");
elt.className = "CodeMirror-ruler"; elt.className = "CodeMirror-ruler";
@ -49,15 +45,7 @@
if (conf.width) elt.style.borderLeftWidth = conf.width; if (conf.width) elt.style.borderLeftWidth = conf.width;
} }
elt.style.left = (left + col * cw) + "px"; elt.style.left = (left + col * cw) + "px";
elt.style.top = "-50px"; cm.state.rulerDiv.appendChild(elt)
elt.style.bottom = "-20px";
elt.style.minHeight = minH + "px";
cm.display.lineSpace.insertBefore(elt, cm.display.cursorDiv);
} }
} }
function refreshRulers(cm) {
clearRulers(cm);
setRulers(cm);
}
}); });

View File

@ -45,7 +45,7 @@
function getConfig(cm) { function getConfig(cm) {
var deflt = cm.state.closeBrackets; var deflt = cm.state.closeBrackets;
if (!deflt) return null; if (!deflt || deflt.override) return deflt;
var mode = cm.getModeAt(cm.getCursor()); var mode = cm.getModeAt(cm.getCursor());
return mode.closeBrackets || deflt; return mode.closeBrackets || deflt;
} }
@ -109,14 +109,16 @@
var ranges = cm.listSelections(); var ranges = cm.listSelections();
var opening = pos % 2 == 0; var opening = pos % 2 == 0;
var type, next; var type;
for (var i = 0; i < ranges.length; i++) { for (var i = 0; i < ranges.length; i++) {
var range = ranges[i], cur = range.head, curType; var range = ranges[i], cur = range.head, curType;
var next = cm.getRange(cur, Pos(cur.line, cur.ch + 1)); var next = cm.getRange(cur, Pos(cur.line, cur.ch + 1));
if (opening && !range.empty()) { if (opening && !range.empty()) {
curType = "surround"; curType = "surround";
} else if ((identical || !opening) && next == ch) { } else if ((identical || !opening) && next == ch) {
if (triples.indexOf(ch) >= 0 && cm.getRange(cur, Pos(cur.line, cur.ch + 3)) == ch + ch + ch) if (identical && stringStartsAfter(cm, cur))
curType = "both";
else if (triples.indexOf(ch) >= 0 && cm.getRange(cur, Pos(cur.line, cur.ch + 3)) == ch + ch + ch)
curType = "skipThree"; curType = "skipThree";
else else
curType = "skip"; curType = "skip";
@ -183,7 +185,7 @@
function enteringString(cm, pos, ch) { function enteringString(cm, pos, ch) {
var line = cm.getLine(pos.line); var line = cm.getLine(pos.line);
var token = cm.getTokenAt(pos); var token = cm.getTokenAt(pos);
if (/\bstring2?\b/.test(token.type)) return false; if (/\bstring2?\b/.test(token.type) || stringStartsAfter(cm, pos)) return false;
var stream = new CodeMirror.StringStream(line.slice(0, pos.ch) + ch + line.slice(pos.ch), 4); var stream = new CodeMirror.StringStream(line.slice(0, pos.ch) + ch + line.slice(pos.ch), 4);
stream.pos = stream.start = token.start; stream.pos = stream.start = token.start;
for (;;) { for (;;) {
@ -192,4 +194,9 @@
stream.start = stream.pos; stream.start = stream.pos;
} }
} }
function stringStartsAfter(cm, pos) {
var token = cm.getTokenAt(Pos(pos.line, pos.ch + 1))
return /\bstring/.test(token.type) && token.start == pos.ch
}
}); });

View File

@ -11,8 +11,8 @@
})(function(CodeMirror) { })(function(CodeMirror) {
"use strict"; "use strict";
var listRE = /^(\s*)(>[> ]*|[*+-]\s|(\d+)([.)]))(\s*)/, var listRE = /^(\s*)(>[> ]*|[*+-] \[[x ]\]\s|[*+-]\s|(\d+)([.)]))(\s*)/,
emptyListRE = /^(\s*)(>[> ]*|[*+-]|(\d+)[.)])(\s*)$/, emptyListRE = /^(\s*)(>[> ]*|[*+-] \[[x ]\]|[*+-]|(\d+)[.)])(\s*)$/,
unorderedListRE = /[*+-]\s/; unorderedListRE = /[*+-]\s/;
CodeMirror.commands.newlineAndIndentContinueMarkdownList = function(cm) { CodeMirror.commands.newlineAndIndentContinueMarkdownList = function(cm) {
@ -30,7 +30,7 @@
return; return;
} }
if (emptyListRE.test(line)) { if (emptyListRE.test(line)) {
cm.replaceRange("", { if (!/>\s*$/.test(line)) cm.replaceRange("", {
line: pos.line, ch: 0 line: pos.line, ch: 0
}, { }, {
line: pos.line, ch: pos.ch + 1 line: pos.line, ch: pos.ch + 1
@ -39,7 +39,7 @@
} else { } else {
var indent = match[1], after = match[5]; var indent = match[1], after = match[5];
var bullet = unorderedListRE.test(match[2]) || match[2].indexOf(">") >= 0 var bullet = unorderedListRE.test(match[2]) || match[2].indexOf(">") >= 0
? match[2] ? match[2].replace("x", " ")
: (parseInt(match[3], 10) + 1) + match[4]; : (parseInt(match[3], 10) + 1) + match[4];
replacements[i] = "\n" + indent + bullet + after; replacements[i] = "\n" + indent + bullet + after;

View File

@ -102,8 +102,10 @@
} }
CodeMirror.defineOption("matchBrackets", false, function(cm, val, old) { CodeMirror.defineOption("matchBrackets", false, function(cm, val, old) {
if (old && old != CodeMirror.Init) if (old && old != CodeMirror.Init) {
cm.off("cursorActivity", doMatchBrackets); cm.off("cursorActivity", doMatchBrackets);
if (currentlyHighlighted) {currentlyHighlighted(); currentlyHighlighted = null;}
}
if (val) { if (val) {
cm.state.matchBrackets = typeof val == "object" ? val : {}; cm.state.matchBrackets = typeof val == "object" ? val : {};
cm.on("cursorActivity", doMatchBrackets); cm.on("cursorActivity", doMatchBrackets);

View File

@ -13,7 +13,7 @@
CodeMirror.registerHelper("fold", "brace", function(cm, start) { CodeMirror.registerHelper("fold", "brace", function(cm, start) {
var line = start.line, lineText = cm.getLine(line); var line = start.line, lineText = cm.getLine(line);
var startCh, tokenType; var tokenType;
function findOpening(openCh) { function findOpening(openCh) {
for (var at = start.ch, pass = 0;;) { for (var at = start.ch, pass = 0;;) {
@ -72,15 +72,15 @@ CodeMirror.registerHelper("fold", "import", function(cm, start) {
} }
} }
var start = start.line, has = hasImport(start), prev; var startLine = start.line, has = hasImport(startLine), prev;
if (!has || hasImport(start - 1) || ((prev = hasImport(start - 2)) && prev.end.line == start - 1)) if (!has || hasImport(startLine - 1) || ((prev = hasImport(startLine - 2)) && prev.end.line == startLine - 1))
return null; return null;
for (var end = has.end;;) { for (var end = has.end;;) {
var next = hasImport(end.line + 1); var next = hasImport(end.line + 1);
if (next == null) break; if (next == null) break;
end = next.end; end = next.end;
} }
return {from: cm.clipPos(CodeMirror.Pos(start, has.startCh + 1)), to: end}; return {from: cm.clipPos(CodeMirror.Pos(startLine, has.startCh + 1)), to: end};
}); });
CodeMirror.registerHelper("fold", "include", function(cm, start) { CodeMirror.registerHelper("fold", "include", function(cm, start) {
@ -91,14 +91,14 @@ CodeMirror.registerHelper("fold", "include", function(cm, start) {
if (start.type == "meta" && start.string.slice(0, 8) == "#include") return start.start + 8; if (start.type == "meta" && start.string.slice(0, 8) == "#include") return start.start + 8;
} }
var start = start.line, has = hasInclude(start); var startLine = start.line, has = hasInclude(startLine);
if (has == null || hasInclude(start - 1) != null) return null; if (has == null || hasInclude(startLine - 1) != null) return null;
for (var end = start;;) { for (var end = startLine;;) {
var next = hasInclude(end + 1); var next = hasInclude(end + 1);
if (next == null) break; if (next == null) break;
++end; ++end;
} }
return {from: CodeMirror.Pos(start, has + 1), return {from: CodeMirror.Pos(startLine, has + 1),
to: cm.clipPos(CodeMirror.Pos(end))}; to: cm.clipPos(CodeMirror.Pos(end))};
}); });

View File

@ -29,7 +29,7 @@ CodeMirror.registerGlobalHelper("fold", "comment", function(mode) {
} }
if (pass == 1 && found < start.ch) return; if (pass == 1 && found < start.ch) return;
if (/comment/.test(cm.getTokenTypeAt(CodeMirror.Pos(line, found + 1))) && if (/comment/.test(cm.getTokenTypeAt(CodeMirror.Pos(line, found + 1))) &&
(lineText.slice(found - endToken.length, found) == endToken || (found == 0 || lineText.slice(found - endToken.length, found) == endToken ||
!/comment/.test(cm.getTokenTypeAt(CodeMirror.Pos(line, found))))) { !/comment/.test(cm.getTokenTypeAt(CodeMirror.Pos(line, found))))) {
startCh = found + startToken.length; startCh = found + startToken.length;
break; break;

View File

@ -49,7 +49,7 @@
}); });
var myRange = cm.markText(range.from, range.to, { var myRange = cm.markText(range.from, range.to, {
replacedWith: myWidget, replacedWith: myWidget,
clearOnEnter: true, clearOnEnter: getOption(cm, options, "clearOnEnter"),
__isFold: true __isFold: true
}); });
myRange.on("clear", function(from, to) { myRange.on("clear", function(from, to) {
@ -129,7 +129,8 @@
rangeFinder: CodeMirror.fold.auto, rangeFinder: CodeMirror.fold.auto,
widget: "\u2194", widget: "\u2194",
minFoldSize: 0, minFoldSize: 0,
scanUp: false scanUp: false,
clearOnEnter: true
}; };
CodeMirror.defineOption("foldOptions", null); CodeMirror.defineOption("foldOptions", null);

View File

@ -50,7 +50,7 @@
} }
function isFolded(cm, line) { function isFolded(cm, line) {
var marks = cm.findMarksAt(Pos(line)); var marks = cm.findMarks(Pos(line, 0), Pos(line + 1, 0));
for (var i = 0; i < marks.length; ++i) for (var i = 0; i < marks.length; ++i)
if (marks[i].__isFold && marks[i].find().from.line == line) return marks[i]; if (marks[i].__isFold && marks[i].find().from.line == line) return marks[i];
} }

View File

@ -11,32 +11,36 @@
})(function(CodeMirror) { })(function(CodeMirror) {
"use strict"; "use strict";
function lineIndent(cm, lineNo) {
var text = cm.getLine(lineNo)
var spaceTo = text.search(/\S/)
if (spaceTo == -1 || /\bcomment\b/.test(cm.getTokenTypeAt(CodeMirror.Pos(lineNo, spaceTo + 1))))
return -1
return CodeMirror.countColumn(text, null, cm.getOption("tabSize"))
}
CodeMirror.registerHelper("fold", "indent", function(cm, start) { CodeMirror.registerHelper("fold", "indent", function(cm, start) {
var tabSize = cm.getOption("tabSize"), firstLine = cm.getLine(start.line); var myIndent = lineIndent(cm, start.line)
if (!/\S/.test(firstLine)) return; if (myIndent < 0) return
var getIndent = function(line) { var lastLineInFold = null
return CodeMirror.countColumn(line, null, tabSize);
};
var myIndent = getIndent(firstLine);
var lastLineInFold = null;
// Go through lines until we find a line that definitely doesn't belong in // Go through lines until we find a line that definitely doesn't belong in
// the block we're folding, or to the end. // the block we're folding, or to the end.
for (var i = start.line + 1, end = cm.lastLine(); i <= end; ++i) { for (var i = start.line + 1, end = cm.lastLine(); i <= end; ++i) {
var curLine = cm.getLine(i); var indent = lineIndent(cm, i)
var curIndent = getIndent(curLine); if (indent == -1) {
if (curIndent > myIndent) { } else if (indent > myIndent) {
// Lines with a greater indent are considered part of the block. // Lines with a greater indent are considered part of the block.
lastLineInFold = i; lastLineInFold = i;
} else if (!/\S/.test(curLine)) {
// Empty lines might be breaks within the block we're trying to fold.
} else { } else {
// A non-empty line at an indent equal to or less than ours marks the // If this line has non-space, non-comment content, and is
// start of another block. // indented less or equal to the start line, it is the start of
// another block.
break; break;
} }
} }
if (lastLineInFold) return { if (lastLineInFold) return {
from: CodeMirror.Pos(start.line, firstLine.length), from: CodeMirror.Pos(start.line, cm.getLine(start.line).length),
to: CodeMirror.Pos(lastLineInFold, cm.getLine(lastLineInFold).length) to: CodeMirror.Pos(lastLineInFold, cm.getLine(lastLineInFold).length)
}; };
}); });

View File

@ -21,8 +21,8 @@
function Iter(cm, line, ch, range) { function Iter(cm, line, ch, range) {
this.line = line; this.ch = ch; this.line = line; this.ch = ch;
this.cm = cm; this.text = cm.getLine(line); this.cm = cm; this.text = cm.getLine(line);
this.min = range ? range.from : cm.firstLine(); this.min = range ? Math.max(range.from, cm.firstLine()) : cm.firstLine();
this.max = range ? range.to - 1 : cm.lastLine(); this.max = range ? Math.min(range.to - 1, cm.lastLine()) : cm.lastLine();
} }
function tagAt(iter, ch) { function tagAt(iter, ch) {
@ -140,9 +140,9 @@
var openTag = toNextTag(iter), end; var openTag = toNextTag(iter), end;
if (!openTag || iter.line != start.line || !(end = toTagEnd(iter))) return; if (!openTag || iter.line != start.line || !(end = toTagEnd(iter))) return;
if (!openTag[1] && end != "selfClose") { if (!openTag[1] && end != "selfClose") {
var start = Pos(iter.line, iter.ch); var startPos = Pos(iter.line, iter.ch);
var close = findMatchingClose(iter, openTag[2]); var endPos = findMatchingClose(iter, openTag[2]);
return close && {from: start, to: close.from}; return endPos && {from: startPos, to: endPos.from};
} }
} }
}); });
@ -163,10 +163,10 @@
} }
}; };
CodeMirror.findEnclosingTag = function(cm, pos, range) { CodeMirror.findEnclosingTag = function(cm, pos, range, tag) {
var iter = new Iter(cm, pos.line, pos.ch, range); var iter = new Iter(cm, pos.line, pos.ch, range);
for (;;) { for (;;) {
var open = findMatchingOpen(iter); var open = findMatchingOpen(iter, tag);
if (!open) break; if (!open) break;
var forward = new Iter(cm, pos.line, pos.ch, range); var forward = new Iter(cm, pos.line, pos.ch, range);
var close = findMatchingClose(forward, open.tag); var close = findMatchingClose(forward, open.tag);

View File

@ -97,6 +97,15 @@
var coffeescriptKeywords = ("and break catch class continue delete do else extends false finally for " + var coffeescriptKeywords = ("and break catch class continue delete do else extends false finally for " +
"if in instanceof isnt new no not null of off on or return switch then throw true try typeof until void while with yes").split(" "); "if in instanceof isnt new no not null of off on or return switch then throw true try typeof until void while with yes").split(" ");
function forAllProps(obj, callback) {
if (!Object.getOwnPropertyNames || !Object.getPrototypeOf) {
for (var name in obj) callback(name)
} else {
for (var o = obj; o; o = Object.getPrototypeOf(o))
Object.getOwnPropertyNames(o).forEach(callback)
}
}
function getCompletions(token, context, keywords, options) { function getCompletions(token, context, keywords, options) {
var found = [], start = token.string, global = options && options.globalScope || window; var found = [], start = token.string, global = options && options.globalScope || window;
function maybeAdd(str) { function maybeAdd(str) {
@ -106,7 +115,7 @@
if (typeof obj == "string") forEach(stringProps, maybeAdd); if (typeof obj == "string") forEach(stringProps, maybeAdd);
else if (obj instanceof Array) forEach(arrayProps, maybeAdd); else if (obj instanceof Array) forEach(arrayProps, maybeAdd);
else if (obj instanceof Function) forEach(funcProps, maybeAdd); else if (obj instanceof Function) forEach(funcProps, maybeAdd);
for (var name in obj) maybeAdd(name); forAllProps(obj, maybeAdd)
} }
if (context && context.length) { if (context && context.length) {

View File

@ -25,8 +25,6 @@
margin: 0; margin: 0;
padding: 0 4px; padding: 0 4px;
border-radius: 2px; border-radius: 2px;
max-width: 19em;
overflow: hidden;
white-space: pre; white-space: pre;
color: black; color: black;
cursor: pointer; cursor: pointer;

View File

@ -229,6 +229,9 @@
var winH = window.innerHeight || Math.max(document.body.offsetHeight, document.documentElement.offsetHeight); var winH = window.innerHeight || Math.max(document.body.offsetHeight, document.documentElement.offsetHeight);
(completion.options.container || document.body).appendChild(hints); (completion.options.container || document.body).appendChild(hints);
var box = hints.getBoundingClientRect(), overlapY = box.bottom - winH; var box = hints.getBoundingClientRect(), overlapY = box.bottom - winH;
var scrolls = hints.scrollHeight > hints.clientHeight + 1
var startScroll = cm.getScrollInfo();
if (overlapY > 0) { if (overlapY > 0) {
var height = box.bottom - box.top, curTop = pos.top - (pos.bottom - box.top); var height = box.bottom - box.top, curTop = pos.top - (pos.bottom - box.top);
if (curTop - height > 0) { // Fits above cursor if (curTop - height > 0) { // Fits above cursor
@ -253,6 +256,8 @@
} }
hints.style.left = (left = pos.left - overlapX) + "px"; hints.style.left = (left = pos.left - overlapX) + "px";
} }
if (scrolls) for (var node = hints.firstChild; node; node = node.nextSibling)
node.style.paddingRight = cm.display.nativeBarWidth + "px"
cm.addKeyMap(this.keyMap = buildKeyMap(completion, { cm.addKeyMap(this.keyMap = buildKeyMap(completion, {
moveFocus: function(n, avoidWrap) { widget.changeActive(widget.selectedHint + n, avoidWrap); }, moveFocus: function(n, avoidWrap) { widget.changeActive(widget.selectedHint + n, avoidWrap); },
@ -270,7 +275,6 @@
cm.on("focus", this.onFocus = function() { clearTimeout(closingOnBlur); }); cm.on("focus", this.onFocus = function() { clearTimeout(closingOnBlur); });
} }
var startScroll = cm.getScrollInfo();
cm.on("scroll", this.onScroll = function() { cm.on("scroll", this.onScroll = function() {
var curScroll = cm.getScrollInfo(), editor = cm.getWrapperElement().getBoundingClientRect(); var curScroll = cm.getScrollInfo(), editor = cm.getWrapperElement().getBoundingClientRect();
var newTop = top + startScroll.top - curScroll.top; var newTop = top + startScroll.top - curScroll.top;

View File

@ -14,11 +14,12 @@
var tables; var tables;
var defaultTable; var defaultTable;
var keywords; var keywords;
var identifierQuote;
var CONS = { var CONS = {
QUERY_DIV: ";", QUERY_DIV: ";",
ALIAS_KEYWORD: "AS" ALIAS_KEYWORD: "AS"
}; };
var Pos = CodeMirror.Pos; var Pos = CodeMirror.Pos, cmpPos = CodeMirror.cmpPos;
function isArray(val) { return Object.prototype.toString.call(val) == "[object Array]" } function isArray(val) { return Object.prototype.toString.call(val) == "[object Array]" }
@ -28,6 +29,12 @@
return CodeMirror.resolveMode(mode).keywords; return CodeMirror.resolveMode(mode).keywords;
} }
function getIdentifierQuote(editor) {
var mode = editor.doc.modeOption;
if (mode === "sql") mode = "text/x-sql";
return CodeMirror.resolveMode(mode).identifierQuote || "`";
}
function getText(item) { function getText(item) {
return typeof item == "string" ? item : item.text; return typeof item == "string" ? item : item.text;
} }
@ -86,17 +93,25 @@
} }
function cleanName(name) { function cleanName(name) {
// Get rid name from backticks(`) and preceding dot(.) // Get rid name from identifierQuote and preceding dot(.)
if (name.charAt(0) == ".") { if (name.charAt(0) == ".") {
name = name.substr(1); name = name.substr(1);
} }
return name.replace(/`/g, ""); // replace doublicated identifierQuotes with single identifierQuotes
// and remove single identifierQuotes
var nameParts = name.split(identifierQuote+identifierQuote);
for (var i = 0; i < nameParts.length; i++)
nameParts[i] = nameParts[i].replace(new RegExp(identifierQuote,"g"), "");
return nameParts.join(identifierQuote);
} }
function insertBackticks(name) { function insertIdentifierQuotes(name) {
var nameParts = getText(name).split("."); var nameParts = getText(name).split(".");
for (var i = 0; i < nameParts.length; i++) for (var i = 0; i < nameParts.length; i++)
nameParts[i] = "`" + nameParts[i] + "`"; nameParts[i] = identifierQuote +
// doublicate identifierQuotes
nameParts[i].replace(new RegExp(identifierQuote,"g"), identifierQuote+identifierQuote) +
identifierQuote;
var escaped = nameParts.join("."); var escaped = nameParts.join(".");
if (typeof name == "string") return escaped; if (typeof name == "string") return escaped;
name = shallowClone(name); name = shallowClone(name);
@ -106,13 +121,13 @@
function nameCompletion(cur, token, result, editor) { function nameCompletion(cur, token, result, editor) {
// Try to complete table, column names and return start position of completion // Try to complete table, column names and return start position of completion
var useBacktick = false; var useIdentifierQuotes = false;
var nameParts = []; var nameParts = [];
var start = token.start; var start = token.start;
var cont = true; var cont = true;
while (cont) { while (cont) {
cont = (token.string.charAt(0) == "."); cont = (token.string.charAt(0) == ".");
useBacktick = useBacktick || (token.string.charAt(0) == "`"); useIdentifierQuotes = useIdentifierQuotes || (token.string.charAt(0) == identifierQuote);
start = token.start; start = token.start;
nameParts.unshift(cleanName(token.string)); nameParts.unshift(cleanName(token.string));
@ -127,12 +142,12 @@
// Try to complete table names // Try to complete table names
var string = nameParts.join("."); var string = nameParts.join(".");
addMatches(result, string, tables, function(w) { addMatches(result, string, tables, function(w) {
return useBacktick ? insertBackticks(w) : w; return useIdentifierQuotes ? insertIdentifierQuotes(w) : w;
}); });
// Try to complete columns from defaultTable // Try to complete columns from defaultTable
addMatches(result, string, defaultTable, function(w) { addMatches(result, string, defaultTable, function(w) {
return useBacktick ? insertBackticks(w) : w; return useIdentifierQuotes ? insertIdentifierQuotes(w) : w;
}); });
// Try to complete columns // Try to complete columns
@ -162,7 +177,7 @@
w = shallowClone(w); w = shallowClone(w);
w.text = tableInsert + "." + w.text; w.text = tableInsert + "." + w.text;
} }
return useBacktick ? insertBackticks(w) : w; return useIdentifierQuotes ? insertIdentifierQuotes(w) : w;
}); });
} }
@ -170,21 +185,9 @@
} }
function eachWord(lineText, f) { function eachWord(lineText, f) {
if (!lineText) return; var words = lineText.split(/\s+/)
var excepted = /[,;]/g; for (var i = 0; i < words.length; i++)
var words = lineText.split(" "); if (words[i]) f(words[i].replace(/[,;]/g, ''))
for (var i = 0; i < words.length; i++) {
f(words[i]?words[i].replace(excepted, '') : '');
}
}
function convertCurToNumber(cur) {
// max characters of a line is 999,999.
return cur.line + cur.ch / Math.pow(10, 6);
}
function convertNumberToCur(num) {
return Pos(Math.floor(num), +num.toString().split('.').pop());
} }
function findTableByAlias(alias, editor) { function findTableByAlias(alias, editor) {
@ -209,15 +212,14 @@
separator.push(Pos(editor.lastLine(), editor.getLineHandle(editor.lastLine()).text.length)); separator.push(Pos(editor.lastLine(), editor.getLineHandle(editor.lastLine()).text.length));
//find valid range //find valid range
var prevItem = 0; var prevItem = null;
var current = convertCurToNumber(editor.getCursor()); var current = editor.getCursor()
for (var i = 0; i < separator.length; i++) { for (var i = 0; i < separator.length; i++) {
var _v = convertCurToNumber(separator[i]); if ((prevItem == null || cmpPos(current, prevItem) > 0) && cmpPos(current, separator[i]) <= 0) {
if (current > prevItem && current <= _v) { validRange = {start: prevItem, end: separator[i]};
validRange = { start: convertNumberToCur(prevItem), end: convertNumberToCur(_v) };
break; break;
} }
prevItem = _v; prevItem = separator[i];
} }
var query = doc.getRange(validRange.start, validRange.end, false); var query = doc.getRange(validRange.start, validRange.end, false);
@ -241,7 +243,8 @@
var defaultTableName = options && options.defaultTable; var defaultTableName = options && options.defaultTable;
var disableKeywords = options && options.disableKeywords; var disableKeywords = options && options.disableKeywords;
defaultTable = defaultTableName && getTable(defaultTableName); defaultTable = defaultTableName && getTable(defaultTableName);
keywords = keywords || getKeywords(editor); keywords = getKeywords(editor);
identifierQuote = getIdentifierQuote(editor);
if (defaultTableName && !defaultTable) if (defaultTableName && !defaultTable)
defaultTable = findTableByAlias(defaultTableName, editor); defaultTable = findTableByAlias(defaultTableName, editor);
@ -259,7 +262,7 @@
token.string = token.string.slice(0, cur.ch - token.start); token.string = token.string.slice(0, cur.ch - token.start);
} }
if (token.string.match(/^[.`\w@]\w*$/)) { if (token.string.match(/^[.`"\w@]\w*$/)) {
search = token.string; search = token.string;
start = token.start; start = token.start;
end = token.end; end = token.end;
@ -267,7 +270,7 @@
start = end = cur.ch; start = end = cur.ch;
search = ""; search = "";
} }
if (search.charAt(0) == "." || search.charAt(0) == "`") { if (search.charAt(0) == "." || search.charAt(0) == identifierQuote) {
start = nameCompletion(cur, token, result, editor); start = nameCompletion(cur, token, result, editor);
} else { } else {
addMatches(result, search, tables, function(w) {return w;}); addMatches(result, search, tables, function(w) {return w;});

View File

@ -4,10 +4,10 @@
} }
.CodeMirror-lint-tooltip { .CodeMirror-lint-tooltip {
background-color: infobackground; background-color: #ffd;
border: 1px solid black; border: 1px solid black;
border-radius: 4px 4px 4px 4px; border-radius: 4px 4px 4px 4px;
color: infotext; color: black;
font-family: monospace; font-family: monospace;
font-size: 10pt; font-size: 10pt;
overflow: hidden; overflow: hidden;

View File

@ -140,7 +140,12 @@
if (options.async || getAnnotations.async) { if (options.async || getAnnotations.async) {
lintAsync(cm, getAnnotations, passOptions) lintAsync(cm, getAnnotations, passOptions)
} else { } else {
updateLinting(cm, getAnnotations(cm.getValue(), passOptions, cm)); var annotations = getAnnotations(cm.getValue(), passOptions, cm);
if (!annotations) return;
if (annotations.then) annotations.then(function(issues) {
updateLinting(cm, issues);
});
else updateLinting(cm, annotations);
} }
} }
@ -226,7 +231,7 @@
var state = cm.state.lint = new LintState(cm, parseOptions(cm, val), hasLintGutter); var state = cm.state.lint = new LintState(cm, parseOptions(cm, val), hasLintGutter);
if (state.options.lintOnChange !== false) if (state.options.lintOnChange !== false)
cm.on("change", onChange); cm.on("change", onChange);
if (state.options.tooltips != false) if (state.options.tooltips != false && state.options.tooltips != "gutter")
CodeMirror.on(cm.getWrapperElement(), "mouseover", state.onMouseOver); CodeMirror.on(cm.getWrapperElement(), "mouseover", state.onMouseOver);
startLinting(cm); startLinting(cm);

View File

@ -19,8 +19,15 @@ CodeMirror.registerHelper("lint", "yaml", function(text) {
var found = []; var found = [];
try { jsyaml.load(text); } try { jsyaml.load(text); }
catch(e) { catch(e) {
var loc = e.mark; var loc = e.mark,
found.push({ from: CodeMirror.Pos(loc.line, loc.column), to: CodeMirror.Pos(loc.line, loc.column), message: e.message }); // js-yaml YAMLException doesn't always provide an accurate lineno
// e.g., when there are multiple yaml docs
// ---
// ---
// foo:bar
from = loc ? CodeMirror.Pos(loc.line, loc.column) : CodeMirror.Pos(0, 0),
to = from;
found.push({ from: from, to: to, message: e.message });
} }
return found; return found;
}); });

View File

@ -37,18 +37,29 @@
constructor: DiffView, constructor: DiffView,
init: function(pane, orig, options) { init: function(pane, orig, options) {
this.edit = this.mv.edit; this.edit = this.mv.edit;
(this.edit.state.diffViews || (this.edit.state.diffViews = [])).push(this); ;(this.edit.state.diffViews || (this.edit.state.diffViews = [])).push(this);
this.orig = CodeMirror(pane, copyObj({value: orig, readOnly: !this.mv.options.allowEditingOriginals}, copyObj(options))); this.orig = CodeMirror(pane, copyObj({value: orig, readOnly: !this.mv.options.allowEditingOriginals}, copyObj(options)));
this.orig.state.diffViews = [this]; if (this.mv.options.connect == "align") {
if (!this.edit.state.trackAlignable) this.edit.state.trackAlignable = new TrackAlignable(this.edit)
this.orig.state.trackAlignable = new TrackAlignable(this.orig)
}
this.diff = getDiff(asString(orig), asString(options.value)); this.orig.state.diffViews = [this];
var classLocation = options.chunkClassLocation || "background";
if (Object.prototype.toString.call(classLocation) != "[object Array]") classLocation = [classLocation]
this.classes.classLocation = classLocation
this.diff = getDiff(asString(orig), asString(options.value), this.mv.options.ignoreWhitespace);
this.chunks = getChunks(this.diff); this.chunks = getChunks(this.diff);
this.diffOutOfDate = this.dealigned = false; this.diffOutOfDate = this.dealigned = false;
this.needsScrollSync = null
this.showDifferences = options.showDifferences !== false; this.showDifferences = options.showDifferences !== false;
},
registerEvents: function(otherDv) {
this.forceUpdate = registerUpdate(this); this.forceUpdate = registerUpdate(this);
setScrollLock(this, true, false); setScrollLock(this, true, false);
registerScroll(this); registerScroll(this, otherDv);
}, },
setShowDifferences: function(val) { setShowDifferences: function(val) {
val = val !== false; val = val !== false;
@ -61,7 +72,7 @@
function ensureDiff(dv) { function ensureDiff(dv) {
if (dv.diffOutOfDate) { if (dv.diffOutOfDate) {
dv.diff = getDiff(dv.orig.getValue(), dv.edit.getValue()); dv.diff = getDiff(dv.orig.getValue(), dv.edit.getValue(), dv.mv.options.ignoreWhitespace);
dv.chunks = getChunks(dv.diff); dv.chunks = getChunks(dv.diff);
dv.diffOutOfDate = false; dv.diffOutOfDate = false;
CodeMirror.signal(dv.edit, "updateDiff", dv.diff); CodeMirror.signal(dv.edit, "updateDiff", dv.diff);
@ -88,10 +99,12 @@
updateMarks(dv.edit, dv.diff, edit, DIFF_INSERT, dv.classes); updateMarks(dv.edit, dv.diff, edit, DIFF_INSERT, dv.classes);
updateMarks(dv.orig, dv.diff, orig, DIFF_DELETE, dv.classes); updateMarks(dv.orig, dv.diff, orig, DIFF_DELETE, dv.classes);
} }
makeConnections(dv);
if (dv.mv.options.connect == "align") if (dv.mv.options.connect == "align")
alignChunks(dv); alignChunks(dv);
makeConnections(dv);
if (dv.needsScrollSync != null) syncScroll(dv, dv.needsScrollSync)
updating = false; updating = false;
} }
function setDealign(fast) { function setDealign(fast) {
@ -113,37 +126,49 @@
// Update faster when a line was added/removed // Update faster when a line was added/removed
setDealign(change.text.length - 1 != change.to.line - change.from.line); setDealign(change.text.length - 1 != change.to.line - change.from.line);
} }
function swapDoc() {
dv.diffOutOfDate = true;
dv.dealigned = true;
update("full");
}
dv.edit.on("change", change); dv.edit.on("change", change);
dv.orig.on("change", change); dv.orig.on("change", change);
dv.edit.on("markerAdded", setDealign); dv.edit.on("swapDoc", swapDoc);
dv.edit.on("markerCleared", setDealign); dv.orig.on("swapDoc", swapDoc);
dv.orig.on("markerAdded", setDealign); if (dv.mv.options.connect == "align") {
dv.orig.on("markerCleared", setDealign); CodeMirror.on(dv.edit.state.trackAlignable, "realign", setDealign)
CodeMirror.on(dv.orig.state.trackAlignable, "realign", setDealign)
}
dv.edit.on("viewportChange", function() { set(false); }); dv.edit.on("viewportChange", function() { set(false); });
dv.orig.on("viewportChange", function() { set(false); }); dv.orig.on("viewportChange", function() { set(false); });
update(); update();
return update; return update;
} }
function registerScroll(dv) { function registerScroll(dv, otherDv) {
dv.edit.on("scroll", function() { dv.edit.on("scroll", function() {
syncScroll(dv, DIFF_INSERT) && makeConnections(dv); syncScroll(dv, true) && makeConnections(dv);
}); });
dv.orig.on("scroll", function() { dv.orig.on("scroll", function() {
syncScroll(dv, DIFF_DELETE) && makeConnections(dv); syncScroll(dv, false) && makeConnections(dv);
if (otherDv) syncScroll(otherDv, true) && makeConnections(otherDv);
}); });
} }
function syncScroll(dv, type) { function syncScroll(dv, toOrig) {
// Change handler will do a refresh after a timeout when diff is out of date // Change handler will do a refresh after a timeout when diff is out of date
if (dv.diffOutOfDate) return false; if (dv.diffOutOfDate) {
if (dv.lockScroll && dv.needsScrollSync == null) dv.needsScrollSync = toOrig
return false
}
dv.needsScrollSync = null
if (!dv.lockScroll) return true; if (!dv.lockScroll) return true;
var editor, other, now = +new Date; var editor, other, now = +new Date;
if (type == DIFF_INSERT) { editor = dv.edit; other = dv.orig; } if (toOrig) { editor = dv.edit; other = dv.orig; }
else { editor = dv.orig; other = dv.edit; } else { editor = dv.orig; other = dv.edit; }
// Don't take action if the position of this editor was recently set // Don't take action if the position of this editor was recently set
// (to prevent feedback loops) // (to prevent feedback loops)
if (editor.state.scrollSetBy == dv && (editor.state.scrollSetAt || 0) + 50 > now) return false; if (editor.state.scrollSetBy == dv && (editor.state.scrollSetAt || 0) + 250 > now) return false;
var sInfo = editor.getScrollInfo(); var sInfo = editor.getScrollInfo();
if (dv.mv.options.connect == "align") { if (dv.mv.options.connect == "align") {
@ -151,9 +176,9 @@
} else { } else {
var halfScreen = .5 * sInfo.clientHeight, midY = sInfo.top + halfScreen; var halfScreen = .5 * sInfo.clientHeight, midY = sInfo.top + halfScreen;
var mid = editor.lineAtHeight(midY, "local"); var mid = editor.lineAtHeight(midY, "local");
var around = chunkBoundariesAround(dv.chunks, mid, type == DIFF_INSERT); var around = chunkBoundariesAround(dv.chunks, mid, toOrig);
var off = getOffsets(editor, type == DIFF_INSERT ? around.edit : around.orig); var off = getOffsets(editor, toOrig ? around.edit : around.orig);
var offOther = getOffsets(other, type == DIFF_INSERT ? around.orig : around.edit); var offOther = getOffsets(other, toOrig ? around.orig : around.edit);
var ratio = (midY - off.top) / (off.bot - off.top); var ratio = (midY - off.top) / (off.bot - off.top);
var targetPos = (offOther.top - halfScreen) + ratio * (offOther.bot - offOther.top); var targetPos = (offOther.top - halfScreen) + ratio * (offOther.bot - offOther.top);
@ -191,16 +216,22 @@
// Updating the marks for editor content // Updating the marks for editor content
function removeClass(editor, line, classes) {
var locs = classes.classLocation
for (var i = 0; i < locs.length; i++) {
editor.removeLineClass(line, locs[i], classes.chunk);
editor.removeLineClass(line, locs[i], classes.start);
editor.removeLineClass(line, locs[i], classes.end);
}
}
function clearMarks(editor, arr, classes) { function clearMarks(editor, arr, classes) {
for (var i = 0; i < arr.length; ++i) { for (var i = 0; i < arr.length; ++i) {
var mark = arr[i]; var mark = arr[i];
if (mark instanceof CodeMirror.TextMarker) { if (mark instanceof CodeMirror.TextMarker)
mark.clear(); mark.clear();
} else if (mark.parent) { else if (mark.parent)
editor.removeLineClass(mark, "background", classes.chunk); removeClass(editor, mark, classes);
editor.removeLineClass(mark, "background", classes.start);
editor.removeLineClass(mark, "background", classes.end);
}
} }
arr.length = 0; arr.length = 0;
} }
@ -226,28 +257,34 @@
}); });
} }
function addClass(editor, lineNr, classes, main, start, end) {
var locs = classes.classLocation, line = editor.getLineHandle(lineNr);
for (var i = 0; i < locs.length; i++) {
if (main) editor.addLineClass(line, locs[i], classes.chunk);
if (start) editor.addLineClass(line, locs[i], classes.start);
if (end) editor.addLineClass(line, locs[i], classes.end);
}
return line;
}
function markChanges(editor, diff, type, marks, from, to, classes) { function markChanges(editor, diff, type, marks, from, to, classes) {
var pos = Pos(0, 0); var pos = Pos(0, 0);
var top = Pos(from, 0), bot = editor.clipPos(Pos(to - 1)); var top = Pos(from, 0), bot = editor.clipPos(Pos(to - 1));
var cls = type == DIFF_DELETE ? classes.del : classes.insert; var cls = type == DIFF_DELETE ? classes.del : classes.insert;
function markChunk(start, end) { function markChunk(start, end) {
var bfrom = Math.max(from, start), bto = Math.min(to, end); var bfrom = Math.max(from, start), bto = Math.min(to, end);
for (var i = bfrom; i < bto; ++i) { for (var i = bfrom; i < bto; ++i)
var line = editor.addLineClass(i, "background", classes.chunk); marks.push(addClass(editor, i, classes, true, i == start, i == end - 1));
if (i == start) editor.addLineClass(line, "background", classes.start);
if (i == end - 1) editor.addLineClass(line, "background", classes.end);
marks.push(line);
}
// When the chunk is empty, make sure a horizontal line shows up // When the chunk is empty, make sure a horizontal line shows up
if (start == end && bfrom == end && bto == end) { if (start == end && bfrom == end && bto == end) {
if (bfrom) if (bfrom)
marks.push(editor.addLineClass(bfrom - 1, "background", classes.end)); marks.push(addClass(editor, bfrom - 1, classes, false, false, true));
else else
marks.push(editor.addLineClass(bfrom, "background", classes.start)); marks.push(addClass(editor, bfrom, classes, false, true, false));
} }
} }
var chunkStart = 0; var chunkStart = 0, pending = false;
for (var i = 0; i < diff.length; ++i) { for (var i = 0; i < diff.length; ++i) {
var part = diff[i], tp = part[0], str = part[1]; var part = diff[i], tp = part[0], str = part[1];
if (tp == DIFF_EQUAL) { if (tp == DIFF_EQUAL) {
@ -255,10 +292,11 @@
moveOver(pos, str); moveOver(pos, str);
var cleanTo = pos.line + (endOfLineClean(diff, i) ? 1 : 0); var cleanTo = pos.line + (endOfLineClean(diff, i) ? 1 : 0);
if (cleanTo > cleanFrom) { if (cleanTo > cleanFrom) {
if (i) markChunk(chunkStart, cleanFrom); if (pending) { markChunk(chunkStart, cleanFrom); pending = false }
chunkStart = cleanTo; chunkStart = cleanTo;
} }
} else { } else {
pending = true
if (tp == type) { if (tp == type) {
var end = moveOver(pos, str, true); var end = moveOver(pos, str, true);
var a = posMax(top, pos), b = posMin(bot, end); var a = posMax(top, pos), b = posMin(bot, end);
@ -268,7 +306,7 @@
} }
} }
} }
if (chunkStart <= pos.line) markChunk(chunkStart, pos.line + 1); if (pending) markChunk(chunkStart, pos.line + 1);
} }
// Updating the gap between editor and original // Updating the gap between editor and original
@ -284,7 +322,9 @@
if (dv.copyButtons) clear(dv.copyButtons); if (dv.copyButtons) clear(dv.copyButtons);
var vpEdit = dv.edit.getViewport(), vpOrig = dv.orig.getViewport(); var vpEdit = dv.edit.getViewport(), vpOrig = dv.orig.getViewport();
var sTopEdit = dv.edit.getScrollInfo().top, sTopOrig = dv.orig.getScrollInfo().top; var outerTop = dv.mv.wrap.getBoundingClientRect().top
var sTopEdit = outerTop - dv.edit.getScrollerElement().getBoundingClientRect().top + dv.edit.getScrollInfo().top
var sTopOrig = outerTop - dv.orig.getScrollerElement().getBoundingClientRect().top + dv.orig.getScrollInfo().top;
for (var i = 0; i < dv.chunks.length; i++) { for (var i = 0; i < dv.chunks.length; i++) {
var ch = dv.chunks[i]; var ch = dv.chunks[i];
if (ch.editFrom <= vpEdit.to && ch.editTo >= vpEdit.from && if (ch.editFrom <= vpEdit.to && ch.editTo >= vpEdit.from &&
@ -305,29 +345,81 @@
return origStart + (editLine - editStart); return origStart + (editLine - editStart);
} }
function findAlignedLines(dv, other) { // Combines information about chunks and widgets/markers to return
var linesToAlign = []; // an array of lines, in a single editor, that probably need to be
for (var i = 0; i < dv.chunks.length; i++) { // aligned with their counterparts in the editor next to it.
var chunk = dv.chunks[i]; function alignableFor(cm, chunks, isOrig) {
linesToAlign.push([chunk.origTo, chunk.editTo, other ? getMatchingOrigLine(chunk.editTo, other.chunks) : null]); var tracker = cm.state.trackAlignable
var start = cm.firstLine(), trackI = 0
var result = []
for (var i = 0;; i++) {
var chunk = chunks[i]
var chunkStart = !chunk ? 1e9 : isOrig ? chunk.origFrom : chunk.editFrom
for (; trackI < tracker.alignable.length; trackI += 2) {
var n = tracker.alignable[trackI] + 1
if (n <= start) continue
if (n <= chunkStart) result.push(n)
else break
}
if (!chunk) break
result.push(start = isOrig ? chunk.origTo : chunk.editTo)
} }
if (other) { return result
for (var i = 0; i < other.chunks.length; i++) { }
var chunk = other.chunks[i];
for (var j = 0; j < linesToAlign.length; j++) { // Given information about alignable lines in two editors, fill in
var align = linesToAlign[j]; // the result (an array of three-element arrays) to reflect the
if (align[1] == chunk.editTo) { // lines that need to be aligned with each other.
j = -1; function mergeAlignable(result, origAlignable, chunks, setIndex) {
break; var rI = 0, origI = 0, chunkI = 0, diff = 0
} else if (align[1] > chunk.editTo) { outer: for (;; rI++) {
break; var nextR = result[rI], nextO = origAlignable[origI]
} if (!nextR && nextO == null) break
var rLine = nextR ? nextR[0] : 1e9, oLine = nextO == null ? 1e9 : nextO
while (chunkI < chunks.length) {
var chunk = chunks[chunkI]
if (chunk.origFrom <= oLine && chunk.origTo > oLine) {
origI++
rI--
continue outer;
} }
if (j > -1) if (chunk.editTo > rLine) {
linesToAlign.splice(j - 1, 0, [getMatchingOrigLine(chunk.editTo, dv.chunks), chunk.editTo, chunk.origTo]); if (chunk.editFrom <= rLine) continue outer;
break
}
diff += (chunk.origTo - chunk.origFrom) - (chunk.editTo - chunk.editFrom)
chunkI++
}
if (rLine == oLine - diff) {
nextR[setIndex] = oLine
origI++
} else if (rLine < oLine - diff) {
nextR[setIndex] = rLine + diff
} else {
var record = [oLine - diff, null, null]
record[setIndex] = oLine
result.splice(rI, 0, record)
origI++
} }
} }
return linesToAlign; }
function findAlignedLines(dv, other) {
var alignable = alignableFor(dv.edit, dv.chunks, false), result = []
if (other) for (var i = 0, j = 0; i < other.chunks.length; i++) {
var n = other.chunks[i].editTo
while (j < alignable.length && alignable[j] < n) j++
if (j == alignable.length || alignable[j] != n) alignable.splice(j++, 0, n)
}
for (var i = 0; i < alignable.length; i++)
result.push([alignable[i], null, null])
mergeAlignable(result, alignableFor(dv.orig, dv.chunks, true), dv.chunks, 1)
if (other)
mergeAlignable(result, alignableFor(other.orig, other.chunks, true), other.chunks, 2)
return result
} }
function alignChunks(dv, force) { function alignChunks(dv, force) {
@ -350,7 +442,7 @@
aligners[i].clear(); aligners[i].clear();
aligners.length = 0; aligners.length = 0;
var cm = [dv.orig, dv.edit], scroll = []; var cm = [dv.edit, dv.orig], scroll = [];
if (other) cm.push(other.orig); if (other) cm.push(other.orig);
for (var i = 0; i < cm.length; i++) for (var i = 0; i < cm.length; i++)
scroll.push(cm[i].getScrollInfo().top); scroll.push(cm[i].getScrollInfo().top);
@ -385,18 +477,18 @@
var elt = document.createElement("div"); var elt = document.createElement("div");
elt.className = "CodeMirror-merge-spacer"; elt.className = "CodeMirror-merge-spacer";
elt.style.height = size + "px"; elt.style.minWidth = "1px"; elt.style.height = size + "px"; elt.style.minWidth = "1px";
return cm.addLineWidget(line, elt, {height: size, above: above}); return cm.addLineWidget(line, elt, {height: size, above: above, mergeSpacer: true, handleMouseEvents: true});
} }
function drawConnectorsForChunk(dv, chunk, sTopOrig, sTopEdit, w) { function drawConnectorsForChunk(dv, chunk, sTopOrig, sTopEdit, w) {
var flip = dv.type == "left"; var flip = dv.type == "left";
var top = dv.orig.heightAtLine(chunk.origFrom, "local") - sTopOrig; var top = dv.orig.heightAtLine(chunk.origFrom, "local", true) - sTopOrig;
if (dv.svg) { if (dv.svg) {
var topLpx = top; var topLpx = top;
var topRpx = dv.edit.heightAtLine(chunk.editFrom, "local") - sTopEdit; var topRpx = dv.edit.heightAtLine(chunk.editFrom, "local", true) - sTopEdit;
if (flip) { var tmp = topLpx; topLpx = topRpx; topRpx = tmp; } if (flip) { var tmp = topLpx; topLpx = topRpx; topRpx = tmp; }
var botLpx = dv.orig.heightAtLine(chunk.origTo, "local") - sTopOrig; var botLpx = dv.orig.heightAtLine(chunk.origTo, "local", true) - sTopOrig;
var botRpx = dv.edit.heightAtLine(chunk.editTo, "local") - sTopEdit; var botRpx = dv.edit.heightAtLine(chunk.editTo, "local", true) - sTopEdit;
if (flip) { var tmp = botLpx; botLpx = botRpx; botRpx = tmp; } if (flip) { var tmp = botLpx; botLpx = botRpx; botRpx = tmp; }
var curveTop = " C " + w/2 + " " + topRpx + " " + w/2 + " " + topLpx + " " + (w + 2) + " " + topLpx; var curveTop = " C " + w/2 + " " + topRpx + " " + w/2 + " " + topLpx + " " + (w + 2) + " " + topLpx;
var curveBot = " C " + w/2 + " " + botLpx + " " + w/2 + " " + botRpx + " -1 " + botRpx; var curveBot = " C " + w/2 + " " + botLpx + " " + w/2 + " " + botRpx + " -1 " + botRpx;
@ -410,10 +502,10 @@
var editOriginals = dv.mv.options.allowEditingOriginals; var editOriginals = dv.mv.options.allowEditingOriginals;
copy.title = editOriginals ? "Push to left" : "Revert chunk"; copy.title = editOriginals ? "Push to left" : "Revert chunk";
copy.chunk = chunk; copy.chunk = chunk;
copy.style.top = top + "px"; copy.style.top = (chunk.origTo > chunk.origFrom ? top : dv.edit.heightAtLine(chunk.editFrom, "local") - sTopEdit) + "px";
if (editOriginals) { if (editOriginals) {
var topReverse = dv.orig.heightAtLine(chunk.editFrom, "local") - sTopEdit; var topReverse = dv.edit.heightAtLine(chunk.editFrom, "local") - sTopEdit;
var copyReverse = dv.copyButtons.appendChild(elt("div", dv.type == "right" ? "\u21dd" : "\u21dc", var copyReverse = dv.copyButtons.appendChild(elt("div", dv.type == "right" ? "\u21dd" : "\u21dc",
"CodeMirror-merge-copy-reverse")); "CodeMirror-merge-copy-reverse"));
copyReverse.title = "Push to right"; copyReverse.title = "Push to right";
@ -427,9 +519,15 @@
function copyChunk(dv, to, from, chunk) { function copyChunk(dv, to, from, chunk) {
if (dv.diffOutOfDate) return; if (dv.diffOutOfDate) return;
var editStart = chunk.editTo > to.lastLine() ? Pos(chunk.editFrom - 1) : Pos(chunk.editFrom, 0)
var origStart = chunk.origTo > from.lastLine() ? Pos(chunk.origFrom - 1) : Pos(chunk.origFrom, 0) var origStart = chunk.origTo > from.lastLine() ? Pos(chunk.origFrom - 1) : Pos(chunk.origFrom, 0)
to.replaceRange(from.getRange(origStart, Pos(chunk.origTo, 0)), editStart, Pos(chunk.editTo, 0)) var origEnd = Pos(chunk.origTo, 0)
var editStart = chunk.editTo > to.lastLine() ? Pos(chunk.editFrom - 1) : Pos(chunk.editFrom, 0)
var editEnd = Pos(chunk.editTo, 0)
var handler = dv.mv.options.revertChunk
if (handler)
handler(dv.mv, from, origStart, origEnd, to, editStart, editEnd)
else
to.replaceRange(from.getRange(origStart, origEnd), editStart, editEnd)
} }
// Merge view, containing 0, 1, or 2 diff views. // Merge view, containing 0, 1, or 2 diff views.
@ -447,18 +545,18 @@
if (hasLeft) { if (hasLeft) {
left = this.left = new DiffView(this, "left"); left = this.left = new DiffView(this, "left");
var leftPane = elt("div", null, "CodeMirror-merge-pane"); var leftPane = elt("div", null, "CodeMirror-merge-pane CodeMirror-merge-left");
wrap.push(leftPane); wrap.push(leftPane);
wrap.push(buildGap(left)); wrap.push(buildGap(left));
} }
var editPane = elt("div", null, "CodeMirror-merge-pane"); var editPane = elt("div", null, "CodeMirror-merge-pane CodeMirror-merge-editor");
wrap.push(editPane); wrap.push(editPane);
if (hasRight) { if (hasRight) {
right = this.right = new DiffView(this, "right"); right = this.right = new DiffView(this, "right");
wrap.push(buildGap(right)); wrap.push(buildGap(right));
var rightPane = elt("div", null, "CodeMirror-merge-pane"); var rightPane = elt("div", null, "CodeMirror-merge-pane CodeMirror-merge-right");
wrap.push(rightPane); wrap.push(rightPane);
} }
@ -471,7 +569,6 @@
if (left) left.init(leftPane, origLeft, options); if (left) left.init(leftPane, origLeft, options);
if (right) right.init(rightPane, origRight, options); if (right) right.init(rightPane, origRight, options);
if (options.collapseIdentical) if (options.collapseIdentical)
this.editor().operation(function() { this.editor().operation(function() {
collapseIdenticalStretches(self, options.collapseIdentical); collapseIdenticalStretches(self, options.collapseIdentical);
@ -480,6 +577,9 @@
this.aligners = []; this.aligners = [];
alignChunks(this.left || this.right, true); alignChunks(this.left || this.right, true);
} }
if (left) left.registerEvents(right)
if (right) right.registerEvents(left)
var onResize = function() { var onResize = function() {
if (left) makeConnections(left); if (left) makeConnections(left);
@ -522,7 +622,7 @@
} }
MergeView.prototype = { MergeView.prototype = {
constuctor: MergeView, constructor: MergeView,
editor: function() { return this.edit; }, editor: function() { return this.edit; },
rightOriginal: function() { return this.right && this.right.orig; }, rightOriginal: function() { return this.right && this.right.orig; },
leftOriginal: function() { return this.left && this.left.orig; }, leftOriginal: function() { return this.left && this.left.orig; },
@ -546,13 +646,12 @@
// Operations on diffs // Operations on diffs
var dmp = new diff_match_patch(); var dmp = new diff_match_patch();
function getDiff(a, b) { function getDiff(a, b, ignoreWhitespace) {
var diff = dmp.diff_main(a, b); var diff = dmp.diff_main(a, b);
dmp.diff_cleanupSemantic(diff);
// The library sometimes leaves in empty parts, which confuse the algorithm // The library sometimes leaves in empty parts, which confuse the algorithm
for (var i = 0; i < diff.length; ++i) { for (var i = 0; i < diff.length; ++i) {
var part = diff[i]; var part = diff[i];
if (!part[1]) { if (ignoreWhitespace ? !/[^ \t]/.test(part[1]) : !part[1]) {
diff.splice(i--, 1); diff.splice(i--, 1);
} else if (i && diff[i - 1][0] == part[0]) { } else if (i && diff[i - 1][0] == part[0]) {
diff.splice(i--, 1); diff.splice(i--, 1);
@ -569,7 +668,7 @@
for (var i = 0; i < diff.length; ++i) { for (var i = 0; i < diff.length; ++i) {
var part = diff[i], tp = part[0]; var part = diff[i], tp = part[0];
if (tp == DIFF_EQUAL) { if (tp == DIFF_EQUAL) {
var startOff = startOfLineClean(diff, i) ? 0 : 1; var startOff = !startOfLineClean(diff, i) || edit.line < startEdit || orig.line < startOrig ? 1 : 0;
var cleanFromEdit = edit.line + startOff, cleanFromOrig = orig.line + startOff; var cleanFromEdit = edit.line + startOff, cleanFromOrig = orig.line + startOff;
moveOver(edit, part[1], null, orig); moveOver(edit, part[1], null, orig);
var endOff = endOfLineClean(diff, i) ? 1 : 0; var endOff = endOfLineClean(diff, i) ? 1 : 0;
@ -592,10 +691,10 @@
function endOfLineClean(diff, i) { function endOfLineClean(diff, i) {
if (i == diff.length - 1) return true; if (i == diff.length - 1) return true;
var next = diff[i + 1][1]; var next = diff[i + 1][1];
if (next.length == 1 || next.charCodeAt(0) != 10) return false; if ((next.length == 1 && i < diff.length - 2) || next.charCodeAt(0) != 10) return false;
if (i == diff.length - 2) return true; if (i == diff.length - 2) return true;
next = diff[i + 2][1]; next = diff[i + 2][1];
return next.length > 1 && next.charCodeAt(0) == 10; return (next.length > 1 || i == diff.length - 3) && next.charCodeAt(0) == 10;
} }
function startOfLineClean(diff, i) { function startOfLineClean(diff, i) {
@ -729,6 +828,131 @@
return out; return out;
} }
// Tracks collapsed markers and line widgets, in order to be able to
// accurately align the content of two editors.
var F_WIDGET = 1, F_WIDGET_BELOW = 2, F_MARKER = 4
function TrackAlignable(cm) {
this.cm = cm
this.alignable = []
this.height = cm.doc.height
var self = this
cm.on("markerAdded", function(_, marker) {
if (!marker.collapsed) return
var found = marker.find(1)
if (found != null) self.set(found.line, F_MARKER)
})
cm.on("markerCleared", function(_, marker, _min, max) {
if (max != null && marker.collapsed)
self.check(max, F_MARKER, self.hasMarker)
})
cm.on("markerChanged", this.signal.bind(this))
cm.on("lineWidgetAdded", function(_, widget, lineNo) {
if (widget.mergeSpacer) return
if (widget.above) self.set(lineNo - 1, F_WIDGET_BELOW)
else self.set(lineNo, F_WIDGET)
})
cm.on("lineWidgetCleared", function(_, widget, lineNo) {
if (widget.mergeSpacer) return
if (widget.above) self.check(lineNo - 1, F_WIDGET_BELOW, self.hasWidgetBelow)
else self.check(lineNo, F_WIDGET, self.hasWidget)
})
cm.on("lineWidgetChanged", this.signal.bind(this))
cm.on("change", function(_, change) {
var start = change.from.line, nBefore = change.to.line - change.from.line
var nAfter = change.text.length - 1, end = start + nAfter
if (nBefore || nAfter) self.map(start, nBefore, nAfter)
self.check(end, F_MARKER, self.hasMarker)
if (nBefore || nAfter) self.check(change.from.line, F_MARKER, self.hasMarker)
})
cm.on("viewportChange", function() {
if (self.cm.doc.height != self.height) self.signal()
})
}
TrackAlignable.prototype = {
signal: function() {
CodeMirror.signal(this, "realign")
this.height = this.cm.doc.height
},
set: function(n, flags) {
var pos = -1
for (; pos < this.alignable.length; pos += 2) {
var diff = this.alignable[pos] - n
if (diff == 0) {
if ((this.alignable[pos + 1] & flags) == flags) return
this.alignable[pos + 1] |= flags
this.signal()
return
}
if (diff > 0) break
}
this.signal()
this.alignable.splice(pos, 0, n, flags)
},
find: function(n) {
for (var i = 0; i < this.alignable.length; i += 2)
if (this.alignable[i] == n) return i
return -1
},
check: function(n, flag, pred) {
var found = this.find(n)
if (found == -1 || !(this.alignable[found + 1] & flag)) return
if (!pred.call(this, n)) {
this.signal()
var flags = this.alignable[found + 1] & ~flag
if (flags) this.alignable[found + 1] = flags
else this.alignable.splice(found, 2)
}
},
hasMarker: function(n) {
var handle = this.cm.getLineHandle(n)
if (handle.markedSpans) for (var i = 0; i < handle.markedSpans.length; i++)
if (handle.markedSpans[i].mark.collapsed && handle.markedSpans[i].to != null)
return true
return false
},
hasWidget: function(n) {
var handle = this.cm.getLineHandle(n)
if (handle.widgets) for (var i = 0; i < handle.widgets.length; i++)
if (!handle.widgets[i].above && !handle.widgets[i].mergeSpacer) return true
return false
},
hasWidgetBelow: function(n) {
if (n == this.cm.lastLine()) return false
var handle = this.cm.getLineHandle(n + 1)
if (handle.widgets) for (var i = 0; i < handle.widgets.length; i++)
if (handle.widgets[i].above && !handle.widgets[i].mergeSpacer) return true
return false
},
map: function(from, nBefore, nAfter) {
var diff = nAfter - nBefore, to = from + nBefore, widgetFrom = -1, widgetTo = -1
for (var i = 0; i < this.alignable.length; i += 2) {
var n = this.alignable[i]
if (n == from && (this.alignable[i + 1] & F_WIDGET_BELOW)) widgetFrom = i
if (n == to && (this.alignable[i + 1] & F_WIDGET_BELOW)) widgetTo = i
if (n <= from) continue
else if (n < to) this.alignable.splice(i--, 2)
else this.alignable[i] += diff
}
if (widgetFrom > -1) {
var flags = this.alignable[widgetFrom + 1]
if (flags == F_WIDGET_BELOW) this.alignable.splice(widgetFrom, 2)
else this.alignable[widgetFrom + 1] = flags & ~F_WIDGET_BELOW
}
if (widgetTo > -1 && nAfter)
this.set(from + nAfter, F_WIDGET_BELOW)
}
}
function posMin(a, b) { return (a.line - b.line || a.ch - b.ch) < 0 ? a : b; } function posMin(a, b) { return (a.line - b.line || a.ch - b.ch) < 0 ? a : b; }
function posMax(a, b) { return (a.line - b.line || a.ch - b.ch) > 0 ? a : b; } function posMax(a, b) { return (a.line - b.line || a.ch - b.ch) > 0 ? a : b; }
function posEq(a, b) { return a.line == b.line && a.ch == b.ch; } function posEq(a, b) { return a.line == b.line && a.ch == b.ch; }

View File

@ -76,8 +76,13 @@ CodeMirror.overlayMode = function(base, overlay, combine) {
innerMode: function(state) { return {state: state.base, mode: base}; }, innerMode: function(state) { return {state: state.base, mode: base}; },
blankLine: function(state) { blankLine: function(state) {
if (base.blankLine) base.blankLine(state.base); var baseToken, overlayToken;
if (overlay.blankLine) overlay.blankLine(state.overlay); if (base.blankLine) baseToken = base.blankLine(state.base);
if (overlay.blankLine) overlayToken = overlay.blankLine(state.overlay);
return overlayToken == null ?
baseToken :
(combine && baseToken != null ? baseToken + " " + overlayToken : overlayToken);
} }
}; };
}; };

View File

@ -77,17 +77,21 @@
curLine = pos.line; curLine = pos.line;
curLineObj = cm.getLineHandle(curLine); curLineObj = cm.getLineHandle(curLine);
} }
if (wrapping && curLineObj.height > singleLineH) if ((curLineObj.widgets && curLineObj.widgets.length) ||
(wrapping && curLineObj.height > singleLineH))
return cm.charCoords(pos, "local")[top ? "top" : "bottom"]; return cm.charCoords(pos, "local")[top ? "top" : "bottom"];
var topY = cm.heightAtLine(curLineObj, "local"); var topY = cm.heightAtLine(curLineObj, "local");
return topY + (top ? 0 : curLineObj.height); return topY + (top ? 0 : curLineObj.height);
} }
var lastLine = cm.lastLine()
if (cm.display.barWidth) for (var i = 0, nextTop; i < anns.length; i++) { if (cm.display.barWidth) for (var i = 0, nextTop; i < anns.length; i++) {
var ann = anns[i]; var ann = anns[i];
if (ann.to.line > lastLine) continue;
var top = nextTop || getY(ann.from, true) * hScale; var top = nextTop || getY(ann.from, true) * hScale;
var bottom = getY(ann.to, false) * hScale; var bottom = getY(ann.to, false) * hScale;
while (i < anns.length - 1) { while (i < anns.length - 1) {
if (anns[i + 1].to.line > lastLine) break;
nextTop = getY(anns[i + 1].from, true) * hScale; nextTop = getY(anns[i + 1].from, true) * hScale;
if (nextTop > bottom + .9) break; if (nextTop > bottom + .9) break;
ann = anns[++i]; ann = anns[++i];

View File

@ -40,7 +40,9 @@
if (cm.state.scrollPastEndPadding != padding) { if (cm.state.scrollPastEndPadding != padding) {
cm.state.scrollPastEndPadding = padding; cm.state.scrollPastEndPadding = padding;
cm.display.lineSpace.parentNode.style.paddingBottom = padding; cm.display.lineSpace.parentNode.style.paddingBottom = padding;
cm.off("refresh", updateBottomMargin);
cm.setSize(); cm.setSize();
cm.on("refresh", updateBottomMargin);
} }
} }
}); });

View File

@ -59,10 +59,10 @@
CodeMirror.on(this.node, "DOMMouseScroll", onWheel); CodeMirror.on(this.node, "DOMMouseScroll", onWheel);
} }
Bar.prototype.setPos = function(pos) { Bar.prototype.setPos = function(pos, force) {
if (pos < 0) pos = 0; if (pos < 0) pos = 0;
if (pos > this.total - this.screen) pos = this.total - this.screen; if (pos > this.total - this.screen) pos = this.total - this.screen;
if (pos == this.pos) return false; if (!force && pos == this.pos) return false;
this.pos = pos; this.pos = pos;
this.inner.style[this.orientation == "horizontal" ? "left" : "top"] = this.inner.style[this.orientation == "horizontal" ? "left" : "top"] =
(pos * (this.size / this.total)) + "px"; (pos * (this.size / this.total)) + "px";
@ -76,9 +76,12 @@
var minButtonSize = 10; var minButtonSize = 10;
Bar.prototype.update = function(scrollSize, clientSize, barSize) { Bar.prototype.update = function(scrollSize, clientSize, barSize) {
this.screen = clientSize; var sizeChanged = this.screen != clientSize || this.total != scrollSize || this.size != barSize
this.total = scrollSize; if (sizeChanged) {
this.size = barSize; this.screen = clientSize;
this.total = scrollSize;
this.size = barSize;
}
var buttonSize = this.screen * (this.size / this.total); var buttonSize = this.screen * (this.size / this.total);
if (buttonSize < minButtonSize) { if (buttonSize < minButtonSize) {
@ -87,7 +90,7 @@
} }
this.inner.style[this.orientation == "horizontal" ? "width" : "height"] = this.inner.style[this.orientation == "horizontal" ? "width" : "height"] =
buttonSize + "px"; buttonSize + "px";
this.setPos(this.pos); this.setPos(this.pos, sizeChanged);
}; };
function SimpleScrollbars(cls, place, scroll) { function SimpleScrollbars(cls, place, scroll) {

View File

@ -45,6 +45,7 @@
this.options[name] = (options && options.hasOwnProperty(name) ? options : defaults)[name] this.options[name] = (options && options.hasOwnProperty(name) ? options : defaults)[name]
this.overlay = this.timeout = null; this.overlay = this.timeout = null;
this.matchesonscroll = null; this.matchesonscroll = null;
this.active = false;
} }
CodeMirror.defineOption("highlightSelectionMatches", false, function(cm, val, old) { CodeMirror.defineOption("highlightSelectionMatches", false, function(cm, val, old) {
@ -53,16 +54,34 @@
clearTimeout(cm.state.matchHighlighter.timeout); clearTimeout(cm.state.matchHighlighter.timeout);
cm.state.matchHighlighter = null; cm.state.matchHighlighter = null;
cm.off("cursorActivity", cursorActivity); cm.off("cursorActivity", cursorActivity);
cm.off("focus", onFocus)
} }
if (val) { if (val) {
cm.state.matchHighlighter = new State(val); var state = cm.state.matchHighlighter = new State(val);
highlightMatches(cm); if (cm.hasFocus()) {
state.active = true
highlightMatches(cm)
} else {
cm.on("focus", onFocus)
}
cm.on("cursorActivity", cursorActivity); cm.on("cursorActivity", cursorActivity);
} }
}); });
function cursorActivity(cm) { function cursorActivity(cm) {
var state = cm.state.matchHighlighter; var state = cm.state.matchHighlighter;
if (state.active || cm.hasFocus()) scheduleHighlight(cm, state)
}
function onFocus(cm) {
var state = cm.state.matchHighlighter
if (!state.active) {
state.active = true
scheduleHighlight(cm, state)
}
}
function scheduleHighlight(cm, state) {
clearTimeout(state.timeout); clearTimeout(state.timeout);
state.timeout = setTimeout(function() {highlightMatches(cm);}, state.options.delay); state.timeout = setTimeout(function() {highlightMatches(cm);}, state.options.delay);
} }
@ -72,7 +91,7 @@
cm.addOverlay(state.overlay = makeOverlay(query, hasBoundary, style)); cm.addOverlay(state.overlay = makeOverlay(query, hasBoundary, style));
if (state.options.annotateScrollbar && cm.showMatchesOnScrollbar) { if (state.options.annotateScrollbar && cm.showMatchesOnScrollbar) {
var searchFor = hasBoundary ? new RegExp("\\b" + query + "\\b") : query; var searchFor = hasBoundary ? new RegExp("\\b" + query + "\\b") : query;
state.matchesonscroll = cm.showMatchesOnScrollbar(searchFor, true, state.matchesonscroll = cm.showMatchesOnScrollbar(searchFor, false,
{className: "CodeMirror-selection-highlight-scrollbar"}); {className: "CodeMirror-selection-highlight-scrollbar"});
} }
} }

View File

@ -54,15 +54,16 @@
function getSearchCursor(cm, query, pos) { function getSearchCursor(cm, query, pos) {
// Heuristic: if the query string is all lowercase, do a case insensitive search. // Heuristic: if the query string is all lowercase, do a case insensitive search.
return cm.getSearchCursor(query, pos, queryCaseInsensitive(query)); return cm.getSearchCursor(query, pos, {caseFold: queryCaseInsensitive(query), multiline: true});
} }
function persistentDialog(cm, text, deflt, f) { function persistentDialog(cm, text, deflt, onEnter, onKeyDown) {
cm.openDialog(text, f, { cm.openDialog(text, onEnter, {
value: deflt, value: deflt,
selectValueOnOpen: true, selectValueOnOpen: true,
closeOnEnter: false, closeOnEnter: false,
onClose: function() { clearSearch(cm); } onClose: function() { clearSearch(cm); },
onKeyDown: onKeyDown
}); });
} }
@ -98,7 +99,7 @@
} }
var queryDialog = var queryDialog =
'Search: <input type="text" style="width: 10em" class="CodeMirror-search-field"/> <span style="color: #888" class="CodeMirror-search-hint">(Use /re/ syntax for regexp search)</span>'; '<span class="CodeMirror-search-label">Search:</span> <input type="text" style="width: 10em" class="CodeMirror-search-field"/> <span style="color: #888" class="CodeMirror-search-hint">(Use /re/ syntax for regexp search)</span>';
function startSearch(cm, state, query) { function startSearch(cm, state, query) {
state.queryText = query; state.queryText = query;
@ -112,13 +113,13 @@
} }
} }
function doSearch(cm, rev, persistent) { function doSearch(cm, rev, persistent, immediate) {
var state = getSearchState(cm); var state = getSearchState(cm);
if (state.query) return findNext(cm, rev); if (state.query) return findNext(cm, rev);
var q = cm.getSelection() || state.lastQuery; var q = cm.getSelection() || state.lastQuery;
if (persistent && cm.openDialog) { if (persistent && cm.openDialog) {
var hiding = null var hiding = null
persistentDialog(cm, queryDialog, q, function(query, event) { var searchNext = function(query, event) {
CodeMirror.e_stop(event); CodeMirror.e_stop(event);
if (!query) return; if (!query) return;
if (query != state.queryText) { if (query != state.queryText) {
@ -133,7 +134,25 @@
dialog.getBoundingClientRect().bottom - 4 > cm.cursorCoords(to, "window").top) dialog.getBoundingClientRect().bottom - 4 > cm.cursorCoords(to, "window").top)
(hiding = dialog).style.opacity = .4 (hiding = dialog).style.opacity = .4
}) })
};
persistentDialog(cm, queryDialog, q, searchNext, function(event, query) {
var keyName = CodeMirror.keyName(event)
var cmd = CodeMirror.keyMap[cm.getOption("keyMap")][keyName]
if (!cmd) cmd = cm.getOption('extraKeys')[keyName]
if (cmd == "findNext" || cmd == "findPrev" ||
cmd == "findPersistentNext" || cmd == "findPersistentPrev") {
CodeMirror.e_stop(event);
startSearch(cm, getSearchState(cm), query);
cm.execCommand(cmd);
} else if (cmd == "find" || cmd == "findPersistent") {
CodeMirror.e_stop(event);
searchNext(query, event);
}
}); });
if (immediate && q) {
startSearch(cm, state, q);
findNext(cm, rev);
}
} else { } else {
dialog(cm, queryDialog, "Search for:", q, function(query) { dialog(cm, queryDialog, "Search for:", q, function(query) {
if (query && !state.query) cm.operation(function() { if (query && !state.query) cm.operation(function() {
@ -169,8 +188,8 @@
var replaceQueryDialog = var replaceQueryDialog =
' <input type="text" style="width: 10em" class="CodeMirror-search-field"/> <span style="color: #888" class="CodeMirror-search-hint">(Use /re/ syntax for regexp search)</span>'; ' <input type="text" style="width: 10em" class="CodeMirror-search-field"/> <span style="color: #888" class="CodeMirror-search-hint">(Use /re/ syntax for regexp search)</span>';
var replacementQueryDialog = 'With: <input type="text" style="width: 10em" class="CodeMirror-search-field"/>'; var replacementQueryDialog = '<span class="CodeMirror-search-label">With:</span> <input type="text" style="width: 10em" class="CodeMirror-search-field"/>';
var doReplaceConfirm = "Replace? <button>Yes</button> <button>No</button> <button>All</button> <button>Stop</button>"; var doReplaceConfirm = '<span class="CodeMirror-search-label">Replace?</span> <button>Yes</button> <button>No</button> <button>All</button> <button>Stop</button>';
function replaceAll(cm, query, text) { function replaceAll(cm, query, text) {
cm.operation(function() { cm.operation(function() {
@ -186,7 +205,7 @@
function replace(cm, all) { function replace(cm, all) {
if (cm.getOption("readOnly")) return; if (cm.getOption("readOnly")) return;
var query = cm.getSelection() || getSearchState(cm).lastQuery; var query = cm.getSelection() || getSearchState(cm).lastQuery;
var dialogText = all ? "Replace all:" : "Replace:" var dialogText = '<span class="CodeMirror-search-label">' + (all ? 'Replace all:' : 'Replace:') + '</span>';
dialog(cm, dialogText + replaceQueryDialog, dialogText, query, function(query) { dialog(cm, dialogText + replaceQueryDialog, dialogText, query, function(query) {
if (!query) return; if (!query) return;
query = parseQuery(query); query = parseQuery(query);
@ -223,6 +242,8 @@
CodeMirror.commands.find = function(cm) {clearSearch(cm); doSearch(cm);}; CodeMirror.commands.find = function(cm) {clearSearch(cm); doSearch(cm);};
CodeMirror.commands.findPersistent = function(cm) {clearSearch(cm); doSearch(cm, false, true);}; CodeMirror.commands.findPersistent = function(cm) {clearSearch(cm); doSearch(cm, false, true);};
CodeMirror.commands.findPersistentNext = function(cm) {doSearch(cm, false, true, true);};
CodeMirror.commands.findPersistentPrev = function(cm) {doSearch(cm, true, true, true);};
CodeMirror.commands.findNext = doSearch; CodeMirror.commands.findNext = doSearch;
CodeMirror.commands.findPrev = function(cm) {doSearch(cm, true);}; CodeMirror.commands.findPrev = function(cm) {doSearch(cm, true);};
CodeMirror.commands.clearSearch = clearSearch; CodeMirror.commands.clearSearch = clearSearch;

View File

@ -3,187 +3,279 @@
(function(mod) { (function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror")); mod(require("../../lib/codemirror"))
else if (typeof define == "function" && define.amd) // AMD else if (typeof define == "function" && define.amd) // AMD
define(["../../lib/codemirror"], mod); define(["../../lib/codemirror"], mod)
else // Plain browser env else // Plain browser env
mod(CodeMirror); mod(CodeMirror)
})(function(CodeMirror) { })(function(CodeMirror) {
"use strict"; "use strict"
var Pos = CodeMirror.Pos; var Pos = CodeMirror.Pos
function SearchCursor(doc, query, pos, caseFold) { function regexpFlags(regexp) {
this.atOccurrence = false; this.doc = doc; var flags = regexp.flags
if (caseFold == null && typeof query == "string") caseFold = false; return flags != null ? flags : (regexp.ignoreCase ? "i" : "")
+ (regexp.global ? "g" : "")
+ (regexp.multiline ? "m" : "")
}
pos = pos ? doc.clipPos(pos) : Pos(0, 0); function ensureGlobal(regexp) {
this.pos = {from: pos, to: pos}; return regexp.global ? regexp : new RegExp(regexp.source, regexpFlags(regexp) + "g")
}
// The matches method is filled in based on the type of query. function maybeMultiline(regexp) {
// It takes a position and a direction, and returns an object return /\\s|\\n|\n|\\W|\\D|\[\^/.test(regexp.source)
// describing the next occurrence of the query, or null if no }
// more matches were found.
if (typeof query != "string") { // Regexp match function searchRegexpForward(doc, regexp, start) {
if (!query.global) query = new RegExp(query.source, query.ignoreCase ? "ig" : "g"); regexp = ensureGlobal(regexp)
this.matches = function(reverse, pos) { for (var line = start.line, ch = start.ch, last = doc.lastLine(); line <= last; line++, ch = 0) {
if (reverse) { regexp.lastIndex = ch
query.lastIndex = 0; var string = doc.getLine(line), match = regexp.exec(string)
var line = doc.getLine(pos.line).slice(0, pos.ch), cutOff = 0, match, start; if (match)
for (;;) { return {from: Pos(line, match.index),
query.lastIndex = cutOff; to: Pos(line, match.index + match[0].length),
var newMatch = query.exec(line); match: match}
if (!newMatch) break; }
match = newMatch; }
start = match.index;
cutOff = match.index + (match[0].length || 1); function searchRegexpForwardMultiline(doc, regexp, start) {
if (cutOff == line.length) break; if (!maybeMultiline(regexp)) return searchRegexpForward(doc, regexp, start)
}
var matchLen = (match && match[0].length) || 0; regexp = ensureGlobal(regexp)
if (!matchLen) { var string, chunk = 1
if (start == 0 && line.length == 0) {match = undefined;} for (var line = start.line, last = doc.lastLine(); line <= last;) {
else if (start != doc.getLine(pos.line).length) { // This grows the search buffer in exponentially-sized chunks
matchLen++; // between matches, so that nearby matches are fast and don't
} // require concatenating the whole document (in case we're
} // searching for something that has tons of matches), but at the
} else { // same time, the amount of retries is limited.
query.lastIndex = pos.ch; for (var i = 0; i < chunk; i++) {
var line = doc.getLine(pos.line), match = query.exec(line); var curLine = doc.getLine(line++)
var matchLen = (match && match[0].length) || 0; string = string == null ? curLine : string + "\n" + curLine
var start = match && match.index; }
if (start + matchLen != line.length && !matchLen) matchLen = 1; chunk = chunk * 2
} regexp.lastIndex = start.ch
if (match && matchLen) var match = regexp.exec(string)
return {from: Pos(pos.line, start), if (match) {
to: Pos(pos.line, start + matchLen), var before = string.slice(0, match.index).split("\n"), inside = match[0].split("\n")
match: match}; var startLine = start.line + before.length - 1, startCh = before[before.length - 1].length
}; return {from: Pos(startLine, startCh),
} else { // String query to: Pos(startLine + inside.length - 1,
var origQuery = query; inside.length == 1 ? startCh + inside[0].length : inside[inside.length - 1].length),
if (caseFold) query = query.toLowerCase(); match: match}
var fold = caseFold ? function(str){return str.toLowerCase();} : function(str){return str;};
var target = query.split("\n");
// Different methods for single-line and multi-line queries
if (target.length == 1) {
if (!query.length) {
// Empty string would match anything and never progress, so
// we define it to match nothing instead.
this.matches = function() {};
} else {
this.matches = function(reverse, pos) {
if (reverse) {
var orig = doc.getLine(pos.line).slice(0, pos.ch), line = fold(orig);
var match = line.lastIndexOf(query);
if (match > -1) {
match = adjustPos(orig, line, match);
return {from: Pos(pos.line, match), to: Pos(pos.line, match + origQuery.length)};
}
} else {
var orig = doc.getLine(pos.line).slice(pos.ch), line = fold(orig);
var match = line.indexOf(query);
if (match > -1) {
match = adjustPos(orig, line, match) + pos.ch;
return {from: Pos(pos.line, match), to: Pos(pos.line, match + origQuery.length)};
}
}
};
}
} else {
var origTarget = origQuery.split("\n");
this.matches = function(reverse, pos) {
var last = target.length - 1;
if (reverse) {
if (pos.line - (target.length - 1) < doc.firstLine()) return;
if (fold(doc.getLine(pos.line).slice(0, origTarget[last].length)) != target[target.length - 1]) return;
var to = Pos(pos.line, origTarget[last].length);
for (var ln = pos.line - 1, i = last - 1; i >= 1; --i, --ln)
if (target[i] != fold(doc.getLine(ln))) return;
var line = doc.getLine(ln), cut = line.length - origTarget[0].length;
if (fold(line.slice(cut)) != target[0]) return;
return {from: Pos(ln, cut), to: to};
} else {
if (pos.line + (target.length - 1) > doc.lastLine()) return;
var line = doc.getLine(pos.line), cut = line.length - origTarget[0].length;
if (fold(line.slice(cut)) != target[0]) return;
var from = Pos(pos.line, cut);
for (var ln = pos.line + 1, i = 1; i < last; ++i, ++ln)
if (target[i] != fold(doc.getLine(ln))) return;
if (fold(doc.getLine(ln).slice(0, origTarget[last].length)) != target[last]) return;
return {from: from, to: Pos(ln, origTarget[last].length)};
}
};
} }
} }
} }
SearchCursor.prototype = { function lastMatchIn(string, regexp) {
findNext: function() {return this.find(false);}, var cutOff = 0, match
findPrevious: function() {return this.find(true);}, for (;;) {
regexp.lastIndex = cutOff
find: function(reverse) { var newMatch = regexp.exec(string)
var self = this, pos = this.doc.clipPos(reverse ? this.pos.from : this.pos.to); if (!newMatch) return match
function savePosAndFail(line) { match = newMatch
var pos = Pos(line, 0); cutOff = match.index + (match[0].length || 1)
self.pos = {from: pos, to: pos}; if (cutOff == string.length) return match
self.atOccurrence = false;
return false;
}
for (;;) {
if (this.pos = this.matches(reverse, pos)) {
this.atOccurrence = true;
return this.pos.match || true;
}
if (reverse) {
if (!pos.line) return savePosAndFail(0);
pos = Pos(pos.line-1, this.doc.getLine(pos.line-1).length);
}
else {
var maxLine = this.doc.lineCount();
if (pos.line == maxLine - 1) return savePosAndFail(maxLine);
pos = Pos(pos.line + 1, 0);
}
}
},
from: function() {if (this.atOccurrence) return this.pos.from;},
to: function() {if (this.atOccurrence) return this.pos.to;},
replace: function(newText, origin) {
if (!this.atOccurrence) return;
var lines = CodeMirror.splitLines(newText);
this.doc.replaceRange(lines, this.pos.from, this.pos.to, origin);
this.pos.to = Pos(this.pos.from.line + lines.length - 1,
lines[lines.length - 1].length + (lines.length == 1 ? this.pos.from.ch : 0));
} }
}; }
function searchRegexpBackward(doc, regexp, start) {
regexp = ensureGlobal(regexp)
for (var line = start.line, ch = start.ch, first = doc.firstLine(); line >= first; line--, ch = -1) {
var string = doc.getLine(line)
if (ch > -1) string = string.slice(0, ch)
var match = lastMatchIn(string, regexp)
if (match)
return {from: Pos(line, match.index),
to: Pos(line, match.index + match[0].length),
match: match}
}
}
function searchRegexpBackwardMultiline(doc, regexp, start) {
regexp = ensureGlobal(regexp)
var string, chunk = 1
for (var line = start.line, first = doc.firstLine(); line >= first;) {
for (var i = 0; i < chunk; i++) {
var curLine = doc.getLine(line--)
string = string == null ? curLine.slice(0, start.ch) : curLine + "\n" + string
}
chunk *= 2
var match = lastMatchIn(string, regexp)
if (match) {
var before = string.slice(0, match.index).split("\n"), inside = match[0].split("\n")
var startLine = line + before.length, startCh = before[before.length - 1].length
return {from: Pos(startLine, startCh),
to: Pos(startLine + inside.length - 1,
inside.length == 1 ? startCh + inside[0].length : inside[inside.length - 1].length),
match: match}
}
}
}
function doFold(str) { return str.toLowerCase() }
function noFold(str) { return str }
// Maps a position in a case-folded line back to a position in the original line // Maps a position in a case-folded line back to a position in the original line
// (compensating for codepoints increasing in number during folding) // (compensating for codepoints increasing in number during folding)
function adjustPos(orig, folded, pos) { function adjustPos(orig, folded, pos) {
if (orig.length == folded.length) return pos; if (orig.length == folded.length) return pos
for (var pos1 = Math.min(pos, orig.length);;) { for (var pos1 = Math.min(pos, orig.length);;) {
var len1 = orig.slice(0, pos1).toLowerCase().length; var len1 = orig.slice(0, pos1).toLowerCase().length
if (len1 < pos) ++pos1; if (len1 < pos) ++pos1
else if (len1 > pos) --pos1; else if (len1 > pos) --pos1
else return pos1; else return pos1
}
}
function searchStringForward(doc, query, start, caseFold) {
// Empty string would match anything and never progress, so we
// define it to match nothing instead.
if (!query.length) return null
var fold = caseFold ? doFold : noFold
var lines = fold(query).split(/\r|\n\r?/)
search: for (var line = start.line, ch = start.ch, last = doc.lastLine() + 1 - lines.length; line <= last; line++, ch = 0) {
var orig = doc.getLine(line).slice(ch), string = fold(orig)
if (lines.length == 1) {
var found = string.indexOf(lines[0])
if (found == -1) continue search
var start = adjustPos(orig, string, found) + ch
return {from: Pos(line, adjustPos(orig, string, found) + ch),
to: Pos(line, adjustPos(orig, string, found + lines[0].length) + ch)}
} else {
var cutFrom = string.length - lines[0].length
if (string.slice(cutFrom) != lines[0]) continue search
for (var i = 1; i < lines.length - 1; i++)
if (fold(doc.getLine(line + i)) != lines[i]) continue search
var end = doc.getLine(line + lines.length - 1), endString = fold(end), lastLine = lines[lines.length - 1]
if (end.slice(0, lastLine.length) != lastLine) continue search
return {from: Pos(line, adjustPos(orig, string, cutFrom) + ch),
to: Pos(line + lines.length - 1, adjustPos(end, endString, lastLine.length))}
}
}
}
function searchStringBackward(doc, query, start, caseFold) {
if (!query.length) return null
var fold = caseFold ? doFold : noFold
var lines = fold(query).split(/\r|\n\r?/)
search: for (var line = start.line, ch = start.ch, first = doc.firstLine() - 1 + lines.length; line >= first; line--, ch = -1) {
var orig = doc.getLine(line)
if (ch > -1) orig = orig.slice(0, ch)
var string = fold(orig)
if (lines.length == 1) {
var found = string.lastIndexOf(lines[0])
if (found == -1) continue search
return {from: Pos(line, adjustPos(orig, string, found)),
to: Pos(line, adjustPos(orig, string, found + lines[0].length))}
} else {
var lastLine = lines[lines.length - 1]
if (string.slice(0, lastLine.length) != lastLine) continue search
for (var i = 1, start = line - lines.length + 1; i < lines.length - 1; i++)
if (fold(doc.getLine(start + i)) != lines[i]) continue search
var top = doc.getLine(line + 1 - lines.length), topString = fold(top)
if (topString.slice(topString.length - lines[0].length) != lines[0]) continue search
return {from: Pos(line + 1 - lines.length, adjustPos(top, topString, top.length - lines[0].length)),
to: Pos(line, adjustPos(orig, string, lastLine.length))}
}
}
}
function SearchCursor(doc, query, pos, options) {
this.atOccurrence = false
this.doc = doc
pos = pos ? doc.clipPos(pos) : Pos(0, 0)
this.pos = {from: pos, to: pos}
var caseFold
if (typeof options == "object") {
caseFold = options.caseFold
} else { // Backwards compat for when caseFold was the 4th argument
caseFold = options
options = null
}
if (typeof query == "string") {
if (caseFold == null) caseFold = false
this.matches = function(reverse, pos) {
return (reverse ? searchStringBackward : searchStringForward)(doc, query, pos, caseFold)
}
} else {
query = ensureGlobal(query)
if (!options || options.multiline !== false)
this.matches = function(reverse, pos) {
return (reverse ? searchRegexpBackwardMultiline : searchRegexpForwardMultiline)(doc, query, pos)
}
else
this.matches = function(reverse, pos) {
return (reverse ? searchRegexpBackward : searchRegexpForward)(doc, query, pos)
}
}
}
SearchCursor.prototype = {
findNext: function() {return this.find(false)},
findPrevious: function() {return this.find(true)},
find: function(reverse) {
var result = this.matches(reverse, this.doc.clipPos(reverse ? this.pos.from : this.pos.to))
// Implements weird auto-growing behavior on null-matches for
// backwards-compatiblity with the vim code (unfortunately)
while (result && CodeMirror.cmpPos(result.from, result.to) == 0) {
if (reverse) {
if (result.from.ch) result.from = Pos(result.from.line, result.from.ch - 1)
else if (result.from.line == this.doc.firstLine()) result = null
else result = this.matches(reverse, this.doc.clipPos(Pos(result.from.line - 1)))
} else {
if (result.to.ch < this.doc.getLine(result.to.line).length) result.to = Pos(result.to.line, result.to.ch + 1)
else if (result.to.line == this.doc.lastLine()) result = null
else result = this.matches(reverse, Pos(result.to.line + 1, 0))
}
}
if (result) {
this.pos = result
this.atOccurrence = true
return this.pos.match || true
} else {
var end = Pos(reverse ? this.doc.firstLine() : this.doc.lastLine() + 1, 0)
this.pos = {from: end, to: end}
return this.atOccurrence = false
}
},
from: function() {if (this.atOccurrence) return this.pos.from},
to: function() {if (this.atOccurrence) return this.pos.to},
replace: function(newText, origin) {
if (!this.atOccurrence) return
var lines = CodeMirror.splitLines(newText)
this.doc.replaceRange(lines, this.pos.from, this.pos.to, origin)
this.pos.to = Pos(this.pos.from.line + lines.length - 1,
lines[lines.length - 1].length + (lines.length == 1 ? this.pos.from.ch : 0))
} }
} }
CodeMirror.defineExtension("getSearchCursor", function(query, pos, caseFold) { CodeMirror.defineExtension("getSearchCursor", function(query, pos, caseFold) {
return new SearchCursor(this.doc, query, pos, caseFold); return new SearchCursor(this.doc, query, pos, caseFold)
}); })
CodeMirror.defineDocExtension("getSearchCursor", function(query, pos, caseFold) { CodeMirror.defineDocExtension("getSearchCursor", function(query, pos, caseFold) {
return new SearchCursor(this, query, pos, caseFold); return new SearchCursor(this, query, pos, caseFold)
}); })
CodeMirror.defineExtension("selectMatches", function(query, caseFold) { CodeMirror.defineExtension("selectMatches", function(query, caseFold) {
var ranges = []; var ranges = []
var cur = this.getSearchCursor(query, this.getCursor("from"), caseFold); var cur = this.getSearchCursor(query, this.getCursor("from"), caseFold)
while (cur.findNext()) { while (cur.findNext()) {
if (CodeMirror.cmpPos(cur.to(), this.getCursor("to")) > 0) break; if (CodeMirror.cmpPos(cur.to(), this.getCursor("to")) > 0) break
ranges.push({anchor: cur.from(), head: cur.to()}); ranges.push({anchor: cur.from(), head: cur.to()})
} }
if (ranges.length) if (ranges.length)
this.setSelections(ranges, 0); this.setSelections(ranges, 0)
}); })
}); });

View File

@ -1,12 +1,6 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others // CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: http://codemirror.net/LICENSE // Distributed under an MIT license: http://codemirror.net/LICENSE
// Because sometimes you need to style the cursor's line.
//
// Adds an option 'styleActiveLine' which, when enabled, gives the
// active line's wrapping <div> the CSS class "CodeMirror-activeline",
// and gives its background <div> the class "CodeMirror-activeline-background".
(function(mod) { (function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror")); mod(require("../../lib/codemirror"));
@ -21,16 +15,18 @@
var GUTT_CLASS = "CodeMirror-activeline-gutter"; var GUTT_CLASS = "CodeMirror-activeline-gutter";
CodeMirror.defineOption("styleActiveLine", false, function(cm, val, old) { CodeMirror.defineOption("styleActiveLine", false, function(cm, val, old) {
var prev = old && old != CodeMirror.Init; var prev = old == CodeMirror.Init ? false : old;
if (val && !prev) { if (val == prev) return
cm.state.activeLines = []; if (prev) {
updateActiveLines(cm, cm.listSelections());
cm.on("beforeSelectionChange", selectionChange);
} else if (!val && prev) {
cm.off("beforeSelectionChange", selectionChange); cm.off("beforeSelectionChange", selectionChange);
clearActiveLines(cm); clearActiveLines(cm);
delete cm.state.activeLines; delete cm.state.activeLines;
} }
if (val) {
cm.state.activeLines = [];
updateActiveLines(cm, cm.listSelections());
cm.on("beforeSelectionChange", selectionChange);
}
}); });
function clearActiveLines(cm) { function clearActiveLines(cm) {
@ -52,7 +48,9 @@
var active = []; var active = [];
for (var i = 0; i < ranges.length; i++) { for (var i = 0; i < ranges.length; i++) {
var range = ranges[i]; var range = ranges[i];
if (!range.empty()) continue; var option = cm.getOption("styleActiveLine");
if (typeof option == "object" && option.nonEmpty ? range.anchor.line != range.head.line : !range.empty())
continue
var line = cm.getLineHandleVisualStart(range.head.line); var line = cm.getLineHandleVisualStart(range.head.line);
if (active[active.length - 1] != line) active.push(line); if (active[active.length - 1] != line) active.push(line);
} }

View File

@ -34,11 +34,12 @@
}); });
function onCursorActivity(cm) { function onCursorActivity(cm) {
cm.operation(function() { update(cm); }); if (cm.state.markedSelection)
cm.operation(function() { update(cm); });
} }
function onChange(cm) { function onChange(cm) {
if (cm.state.markedSelection.length) if (cm.state.markedSelection && cm.state.markedSelection.length)
cm.operation(function() { clear(cm); }); cm.operation(function() { clear(cm); });
} }

View File

@ -30,7 +30,9 @@
} }
function findBreakPoint(text, column, wrapOn, killTrailingSpace) { function findBreakPoint(text, column, wrapOn, killTrailingSpace) {
for (var at = column; at > 0; --at) var at = column
while (at < text.length && text.charAt(at) == " ") at++
for (; at > 0; --at)
if (wrapOn.test(text.slice(at - 1, at + 1))) break; if (wrapOn.test(text.slice(at - 1, at + 1))) break;
for (var first = true;; first = false) { for (var first = true;; first = false) {
var endOfText = at; var endOfText = at;

View File

@ -52,7 +52,7 @@
} }
.cm-fat-cursor .CodeMirror-cursor { .cm-fat-cursor .CodeMirror-cursor {
width: auto; width: auto;
border: 0; border: 0 !important;
background: #7e7; background: #7e7;
} }
.cm-fat-cursor div.CodeMirror-cursors { .cm-fat-cursor div.CodeMirror-cursors {
@ -88,8 +88,14 @@
.cm-tab { display: inline-block; text-decoration: inherit; } .cm-tab { display: inline-block; text-decoration: inherit; }
.CodeMirror-rulers {
position: absolute;
left: 0; right: 0; top: -50px; bottom: -20px;
overflow: hidden;
}
.CodeMirror-ruler { .CodeMirror-ruler {
border-left: 1px solid #ccc; border-left: 1px solid #ccc;
top: 0; bottom: 0;
position: absolute; position: absolute;
} }
@ -113,7 +119,7 @@
.cm-s-default .cm-property, .cm-s-default .cm-property,
.cm-s-default .cm-operator {} .cm-s-default .cm-operator {}
.cm-s-default .cm-variable-2 {color: #05a;} .cm-s-default .cm-variable-2 {color: #05a;}
.cm-s-default .cm-variable-3 {color: #085;} .cm-s-default .cm-variable-3, .cm-s-default .cm-type {color: #085;}
.cm-s-default .cm-comment {color: #a50;} .cm-s-default .cm-comment {color: #a50;}
.cm-s-default .cm-string {color: #a11;} .cm-s-default .cm-string {color: #a11;}
.cm-s-default .cm-string-2 {color: #f50;} .cm-s-default .cm-string-2 {color: #f50;}
@ -200,9 +206,6 @@ div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;}
display: inline-block; display: inline-block;
vertical-align: top; vertical-align: top;
margin-bottom: -30px; margin-bottom: -30px;
/* Hack to make IE7 behave */
*zoom:1;
*display:inline;
} }
.CodeMirror-gutter-wrapper { .CodeMirror-gutter-wrapper {
position: absolute; position: absolute;
@ -220,11 +223,8 @@ div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;}
cursor: default; cursor: default;
z-index: 4; z-index: 4;
} }
.CodeMirror-gutter-wrapper { .CodeMirror-gutter-wrapper ::selection { background-color: transparent }
-webkit-user-select: none; .CodeMirror-gutter-wrapper ::-moz-selection { background-color: transparent }
-moz-user-select: none;
user-select: none;
}
.CodeMirror-lines { .CodeMirror-lines {
cursor: text; cursor: text;
@ -246,8 +246,8 @@ div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;}
position: relative; position: relative;
overflow: visible; overflow: visible;
-webkit-tap-highlight-color: transparent; -webkit-tap-highlight-color: transparent;
-webkit-font-variant-ligatures: none; -webkit-font-variant-ligatures: contextual;
font-variant-ligatures: none; font-variant-ligatures: contextual;
} }
.CodeMirror-wrap pre { .CodeMirror-wrap pre {
word-wrap: break-word; word-wrap: break-word;
@ -269,6 +269,8 @@ div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;}
.CodeMirror-widget {} .CodeMirror-widget {}
.CodeMirror-rtl pre { direction: rtl; }
.CodeMirror-code { .CodeMirror-code {
outline: none; outline: none;
} }
@ -291,7 +293,10 @@ div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;}
visibility: hidden; visibility: hidden;
} }
.CodeMirror-cursor { position: absolute; } .CodeMirror-cursor {
position: absolute;
pointer-events: none;
}
.CodeMirror-measure pre { position: static; } .CodeMirror-measure pre { position: static; }
div.CodeMirror-cursors { div.CodeMirror-cursors {
@ -318,9 +323,6 @@ div.CodeMirror-dragcursors {
background: rgba(255, 255, 0, .4); background: rgba(255, 255, 0, .4);
} }
/* IE7 hack to prevent it from returning funny offsetTops on the spans */
.CodeMirror span { *vertical-align: text-bottom; }
/* Used to force a border model for a node */ /* Used to force a border model for a node */
.cm-force-border { padding-right: .1px; } .cm-force-border { padding-right: .1px; }

File diff suppressed because it is too large Load Diff

View File

@ -32,13 +32,13 @@ CodeMirror.defineMode("sql", function(config, parserConfig) {
if (result !== false) return result; if (result !== false) return result;
} }
if (support.hexNumber == true && if (support.hexNumber &&
((ch == "0" && stream.match(/^[xX][0-9a-fA-F]+/)) ((ch == "0" && stream.match(/^[xX][0-9a-fA-F]+/))
|| (ch == "x" || ch == "X") && stream.match(/^'[0-9a-fA-F]+'/))) { || (ch == "x" || ch == "X") && stream.match(/^'[0-9a-fA-F]+'/))) {
// hex // hex
// ref: http://dev.mysql.com/doc/refman/5.5/en/hexadecimal-literals.html // ref: http://dev.mysql.com/doc/refman/5.5/en/hexadecimal-literals.html
return "number"; return "number";
} else if (support.binaryNumber == true && } else if (support.binaryNumber &&
(((ch == "b" || ch == "B") && stream.match(/^'[01]+'/)) (((ch == "b" || ch == "B") && stream.match(/^'[01]+'/))
|| (ch == "0" && stream.match(/^b[01]+/)))) { || (ch == "0" && stream.match(/^b[01]+/)))) {
// bitstring // bitstring
@ -48,7 +48,7 @@ CodeMirror.defineMode("sql", function(config, parserConfig) {
// numbers // numbers
// ref: http://dev.mysql.com/doc/refman/5.5/en/number-literals.html // ref: http://dev.mysql.com/doc/refman/5.5/en/number-literals.html
stream.match(/^[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?/); stream.match(/^[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?/);
support.decimallessFloat == true && stream.eat('.'); support.decimallessFloat && stream.eat('.');
return "number"; return "number";
} else if (ch == "?" && (stream.eatSpace() || stream.eol() || stream.eat(";"))) { } else if (ch == "?" && (stream.eatSpace() || stream.eol() || stream.eat(";"))) {
// placeholders // placeholders
@ -58,8 +58,8 @@ CodeMirror.defineMode("sql", function(config, parserConfig) {
// ref: http://dev.mysql.com/doc/refman/5.5/en/string-literals.html // ref: http://dev.mysql.com/doc/refman/5.5/en/string-literals.html
state.tokenize = tokenLiteral(ch); state.tokenize = tokenLiteral(ch);
return state.tokenize(stream, state); return state.tokenize(stream, state);
} else if ((((support.nCharCast == true && (ch == "n" || ch == "N")) } else if ((((support.nCharCast && (ch == "n" || ch == "N"))
|| (support.charsetCast == true && ch == "_" && stream.match(/[a-z][a-z0-9]*/i))) || (support.charsetCast && ch == "_" && stream.match(/[a-z][a-z0-9]*/i)))
&& (stream.peek() == "'" || stream.peek() == '"'))) { && (stream.peek() == "'" || stream.peek() == '"'))) {
// charset casting: _utf8'str', N'str', n'str' // charset casting: _utf8'str', N'str', n'str'
// ref: http://dev.mysql.com/doc/refman/5.5/en/string-literals.html // ref: http://dev.mysql.com/doc/refman/5.5/en/string-literals.html
@ -80,16 +80,16 @@ CodeMirror.defineMode("sql", function(config, parserConfig) {
} else if (ch == "/" && stream.eat("*")) { } else if (ch == "/" && stream.eat("*")) {
// multi-line comments // multi-line comments
// ref: https://kb.askmonty.org/en/comment-syntax/ // ref: https://kb.askmonty.org/en/comment-syntax/
state.tokenize = tokenComment; state.tokenize = tokenComment(1);
return state.tokenize(stream, state); return state.tokenize(stream, state);
} else if (ch == ".") { } else if (ch == ".") {
// .1 for 0.1 // .1 for 0.1
if (support.zerolessFloat == true && stream.match(/^(?:\d+(?:e[+-]?\d+)?)/i)) { if (support.zerolessFloat && stream.match(/^(?:\d+(?:e[+-]?\d+)?)/i)) {
return "number"; return "number";
} }
// .table_name (ODBC) // .table_name (ODBC)
// // ref: http://dev.mysql.com/doc/refman/5.6/en/identifier-qualifiers.html // // ref: http://dev.mysql.com/doc/refman/5.6/en/identifier-qualifiers.html
if (support.ODBCdotTable == true && stream.match(/^[a-zA-Z_]+/)) { if (support.ODBCdotTable && stream.match(/^[a-zA-Z_]+/)) {
return "variable-2"; return "variable-2";
} }
} else if (operatorChars.test(ch)) { } else if (operatorChars.test(ch)) {
@ -130,20 +130,15 @@ CodeMirror.defineMode("sql", function(config, parserConfig) {
return "string"; return "string";
}; };
} }
function tokenComment(stream, state) { function tokenComment(depth) {
while (true) { return function(stream, state) {
if (stream.skipTo("*")) { var m = stream.match(/^.*?(\/\*|\*\/)/)
stream.next(); if (!m) stream.skipToEnd()
if (stream.eat("/")) { else if (m[1] == "/*") state.tokenize = tokenComment(depth + 1)
state.tokenize = tokenBase; else if (depth > 1) state.tokenize = tokenComment(depth - 1)
break; else state.tokenize = tokenBase
} return "comment"
} else {
stream.skipToEnd();
break;
}
} }
return "comment";
} }
function pushContext(stream, state, type) { function pushContext(stream, state, type) {
@ -170,7 +165,7 @@ CodeMirror.defineMode("sql", function(config, parserConfig) {
if (state.context && state.context.align == null) if (state.context && state.context.align == null)
state.context.align = false; state.context.align = false;
} }
if (stream.eatSpace()) return null; if (state.tokenize == tokenBase && stream.eatSpace()) return null;
var style = state.tokenize(stream, state); var style = state.tokenize(stream, state);
if (style == "comment") return style; if (style == "comment") return style;
@ -217,6 +212,19 @@ CodeMirror.defineMode("sql", function(config, parserConfig) {
return stream.eatWhile(/\w/) ? "variable-2" : null; return stream.eatWhile(/\w/) ? "variable-2" : null;
} }
// "identifier"
function hookIdentifierDoublequote(stream) {
// Standard SQL /SQLite identifiers
// ref: http://web.archive.org/web/20160813185132/http://savage.net.au/SQL/sql-99.bnf.html#delimited%20identifier
// ref: http://sqlite.org/lang_keywords.html
var ch;
while ((ch = stream.next()) != null) {
if (ch == "\"" && !stream.eat("\"")) return "variable-2";
}
stream.backUp(stream.current().length - 1);
return stream.eatWhile(/\w/) ? "variable-2" : null;
}
// variable token // variable token
function hookVar(stream) { function hookVar(stream) {
// variables // variables
@ -280,7 +288,7 @@ CodeMirror.defineMode("sql", function(config, parserConfig) {
CodeMirror.defineMIME("text/x-mssql", { CodeMirror.defineMIME("text/x-mssql", {
name: "sql", name: "sql",
client: set("charset clear connect edit ego exit go help nopager notee nowarning pager print prompt quit rehash source status system tee"), client: set("charset clear connect edit ego exit go help nopager notee nowarning pager print prompt quit rehash source status system tee"),
keywords: set(sqlKeywords + "begin trigger proc view index for add constraint key primary foreign collate clustered nonclustered declare"), keywords: set(sqlKeywords + "begin trigger proc view index for add constraint key primary foreign collate clustered nonclustered declare exec"),
builtin: set("bigint numeric bit smallint decimal smallmoney int tinyint money float real char varchar text nchar nvarchar ntext binary varbinary image cursor timestamp hierarchyid uniqueidentifier sql_variant xml table "), builtin: set("bigint numeric bit smallint decimal smallmoney int tinyint money float real char varchar text nchar nvarchar ntext binary varbinary image cursor timestamp hierarchyid uniqueidentifier sql_variant xml table "),
atoms: set("false true null unknown"), atoms: set("false true null unknown"),
operatorChars: /^[*+\-%<>!=]/, operatorChars: /^[*+\-%<>!=]/,
@ -322,6 +330,36 @@ CodeMirror.defineMode("sql", function(config, parserConfig) {
} }
}); });
// provided by the phpLiteAdmin project - phpliteadmin.org
CodeMirror.defineMIME("text/x-sqlite", {
name: "sql",
// commands of the official SQLite client, ref: https://www.sqlite.org/cli.html#dotcmd
client: set("auth backup bail binary changes check clone databases dbinfo dump echo eqp exit explain fullschema headers help import imposter indexes iotrace limit lint load log mode nullvalue once open output print prompt quit read restore save scanstats schema separator session shell show stats system tables testcase timeout timer trace vfsinfo vfslist vfsname width"),
// ref: http://sqlite.org/lang_keywords.html
keywords: set(sqlKeywords + "abort action add after all analyze attach autoincrement before begin cascade case cast check collate column commit conflict constraint cross current_date current_time current_timestamp database default deferrable deferred detach each else end escape except exclusive exists explain fail for foreign full glob if ignore immediate index indexed initially inner instead intersect isnull key left limit match natural no notnull null of offset outer plan pragma primary query raise recursive references regexp reindex release rename replace restrict right rollback row savepoint temp temporary then to transaction trigger unique using vacuum view virtual when with without"),
// SQLite is weakly typed, ref: http://sqlite.org/datatype3.html. This is just a list of some common types.
builtin: set("bool boolean bit blob decimal double float long longblob longtext medium mediumblob mediumint mediumtext time timestamp tinyblob tinyint tinytext text clob bigint int int2 int8 integer float double char varchar date datetime year unsigned signed numeric real"),
// ref: http://sqlite.org/syntax/literal-value.html
atoms: set("null current_date current_time current_timestamp"),
// ref: http://sqlite.org/lang_expr.html#binaryops
operatorChars: /^[*+\-%<>!=&|/~]/,
// SQLite is weakly typed, ref: http://sqlite.org/datatype3.html. This is just a list of some common types.
dateSQL: set("date time timestamp datetime"),
support: set("decimallessFloat zerolessFloat"),
identifierQuote: "\"", //ref: http://sqlite.org/lang_keywords.html
hooks: {
// bind-parameters ref:http://sqlite.org/lang_expr.html#varparam
"@": hookVar,
":": hookVar,
"?": hookVar,
"$": hookVar,
// The preferred way to escape Identifiers is using double quotes, ref: http://sqlite.org/lang_keywords.html
"\"": hookIdentifierDoublequote,
// there is also support for backtics, ref: http://sqlite.org/lang_keywords.html
"`": hookIdentifier
}
});
// the query language used by Apache Cassandra is called CQL, but this mime type // the query language used by Apache Cassandra is called CQL, but this mime type
// is called Cassandra to avoid confusion with Contextual Query Language // is called Cassandra to avoid confusion with Contextual Query Language
CodeMirror.defineMIME("text/x-cassandra", { CodeMirror.defineMIME("text/x-cassandra", {
@ -366,9 +404,9 @@ CodeMirror.defineMode("sql", function(config, parserConfig) {
// http://www.postgresql.org/docs/9.5/static/datatype.html // http://www.postgresql.org/docs/9.5/static/datatype.html
builtin: set("bigint int8 bigserial serial8 bit varying varbit boolean bool box bytea character char varchar cidr circle date double precision float8 inet integer int int4 interval json jsonb line lseg macaddr money numeric decimal path pg_lsn point polygon real float4 smallint int2 smallserial serial2 serial serial4 text time without zone with timetz timestamp timestamptz tsquery tsvector txid_snapshot uuid xml"), builtin: set("bigint int8 bigserial serial8 bit varying varbit boolean bool box bytea character char varchar cidr circle date double precision float8 inet integer int int4 interval json jsonb line lseg macaddr money numeric decimal path pg_lsn point polygon real float4 smallint int2 smallserial serial2 serial serial4 text time without zone with timetz timestamp timestamptz tsquery tsvector txid_snapshot uuid xml"),
atoms: set("false true null unknown"), atoms: set("false true null unknown"),
operatorChars: /^[*+\-%<>!=&|^]/, operatorChars: /^[*+\-%<>!=&|^\/#@?~]/,
dateSQL: set("date time timestamp"), dateSQL: set("date time timestamp"),
support: set("ODBCdotTable decimallessFloat zerolessFloat binaryNumber hexNumber nCharCast charsetCast commentHash commentSpaceRequired") support: set("ODBCdotTable decimallessFloat zerolessFloat binaryNumber hexNumber nCharCast charsetCast")
}); });
// Google's SQL-like query language, GQL // Google's SQL-like query language, GQL

View File

@ -59,6 +59,7 @@
<link type="text/css" rel="stylesheet" href="{{ url_for('static', filename='css/codemirror.overrides.css') }}"/> <link type="text/css" rel="stylesheet" href="{{ url_for('static', filename='css/codemirror.overrides.css') }}"/>
<link type="text/css" rel="stylesheet" href="{{ url_for('static', filename='css/pgadmin.css') }}"/> <link type="text/css" rel="stylesheet" href="{{ url_for('static', filename='css/pgadmin.css') }}"/>
<link type="text/css" rel="stylesheet" href="{{ url_for('static', filename='css/pgadmin.style.css') }}"/> <link type="text/css" rel="stylesheet" href="{{ url_for('static', filename='css/pgadmin.style.css') }}"/>
<link type="text/css" rel="stylesheet" href="{{ url_for('static', filename='css/codemirror.overrides.css') }}"/>
{% block css_link %}{% endblock %} {% block css_link %}{% endblock %}
<!-- Base template scripts --> <!-- Base template scripts -->

View File

@ -973,14 +973,9 @@ define([
render_history_grid: function() { render_history_grid: function() {
var self = this; var self = this;
// Remove any existing grid first self.history_collection = new HistoryBundle.HistoryCollection([]);
if (self.history_grid) {
self.history_grid.remove();
}
self.history_collection = new HistoryBundle.historyCollection([]); var queryHistoryElement = reactComponents.React.createElement(
let queryHistoryElement = reactComponents.React.createElement(
reactComponents.QueryHistory, {historyCollection: self.history_collection}); reactComponents.QueryHistory, {historyCollection: self.history_collection});
reactComponents.render(queryHistoryElement, $('#history_grid')[0]); reactComponents.render(queryHistoryElement, $('#history_grid')[0]);
}, },
@ -1771,6 +1766,7 @@ define([
}, },
error: function(e) { error: function(e) {
// Enable/Disable query tool button only if is_query_tool is true. // Enable/Disable query tool button only if is_query_tool is true.
self.resetQueryHistoryObject(self);
self.trigger('pgadmin-sqleditor:loading-icon:hide'); self.trigger('pgadmin-sqleditor:loading-icon:hide');
if (self.is_query_tool) { if (self.is_query_tool) {
self.disable_tool_buttons(false); self.disable_tool_buttons(false);
@ -1856,7 +1852,6 @@ define([
// Show message in message and history tab in case of query tool // Show message in message and history tab in case of query tool
self.total_time = self.get_query_run_time(self.query_start_time, self.query_end_time); self.total_time = self.get_query_run_time(self.query_start_time, self.query_end_time);
self.update_msg_history(true, "", false);
var msg1 = S(gettext("Total query runtime: %s.")).sprintf(self.total_time).value(); var msg1 = S(gettext("Total query runtime: %s.")).sprintf(self.total_time).value();
var msg2 = S(gettext("%s rows affected.")).sprintf(self.rows_affected).value(); var msg2 = S(gettext("%s rows affected.")).sprintf(self.rows_affected).value();
@ -1867,6 +1862,7 @@ define([
var _msg = msg1 + '\n' + msg2; var _msg = msg1 + '\n' + msg2;
self.update_msg_history(true, _msg, false);
// If there is additional messages from server then add it to message // If there is additional messages from server then add it to message
if(!_.isNull(data.additional_messages) && if(!_.isNull(data.additional_messages) &&
!_.isUndefined(data.additional_messages)) { !_.isUndefined(data.additional_messages)) {
@ -2030,6 +2026,10 @@ define([
} }
}, },
resetQueryHistoryObject: function (history) {
history.total_time = '-';
},
// This function is used to raise appropriate message. // This function is used to raise appropriate message.
update_msg_history: function(status, msg, clear_grid) { update_msg_history: function(status, msg, clear_grid) {
var self = this; var self = this;

View File

@ -41,7 +41,6 @@ class AppStarter:
) )
self.driver.set_window_size(1024, 1024) self.driver.set_window_size(1024, 1024)
time.sleep(10)
self.driver.get( self.driver.get(
"http://" + self.app_config.DEFAULT_SERVER + ":" + "http://" + self.app_config.DEFAULT_SERVER + ":" +
random_server_port) random_server_port)

View File

@ -27,7 +27,7 @@ class PgadminPage:
def __init__(self, driver, app_config): def __init__(self, driver, app_config):
self.driver = driver self.driver = driver
self.app_config = app_config self.app_config = app_config
self.timeout = 20 self.timeout = 30
self.app_start_timeout = 60 self.app_start_timeout = 60
def reset_layout(self): def reset_layout(self):

View File

@ -0,0 +1,68 @@
/////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2017, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////
import React from 'react';
import $ from '../../pgadmin/static/vendor/jquery/jquery-1.11.2';
import CodeMirror from '../../pgadmin/static/jsx/history/detail/code_mirror';
import jasmineEnzyme from 'jasmine-enzyme';
import {shallow} from 'enzyme';
describe('CodeMirror', () => {
beforeEach(() => {
jasmineEnzyme();
});
describe('#hydrateWhenBecomesVisible', () => {
let codeMirror, isVisibleSpy;
beforeEach(() => {
codeMirror = shallow(<CodeMirror />).instance();
isVisibleSpy = spyOn($.fn, 'is');
spyOn(codeMirror, 'hydrate');
});
describe('when component is visible', () => {
beforeEach(() => {
isVisibleSpy.and.returnValue(true);
});
it('should hydrate the codemirror element', () => {
codeMirror.hydrateWhenBecomesVisible();
expect(codeMirror.hydrate).toHaveBeenCalledTimes(1);
});
});
describe('when component is not visible', () => {
beforeEach(() => {
isVisibleSpy.and.returnValue(false);
});
it('should not hydrate the codemirror element', () => {
codeMirror.hydrateWhenBecomesVisible();
expect(codeMirror.hydrate).not.toHaveBeenCalled();
});
describe('when becomes visible', () => {
beforeEach(() => {
isVisibleSpy.and.returnValue(true);
});
it('should hydrate the codemirror element', (done) => {
setTimeout(() => {
codeMirror.hydrateWhenBecomesVisible();
expect(codeMirror.hydrate).toHaveBeenCalledTimes(1);
done();
}, 150);
});
});
});
});
});

View File

@ -66,10 +66,6 @@ describe('historyCollection', function () {
}); });
}); });
describe('sort', function () {
it('doesn\'t sort');
});
describe('when instantiated', function () { describe('when instantiated', function () {
describe('from a history model', function () { describe('from a history model', function () {
it('has the historyModel', () => { it('has the historyModel', () => {

View File

@ -1,50 +0,0 @@
/////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2017, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////
import React from 'react';
import QueryHistoryEntry from '../../../pgadmin/static/jsx/history/query_history_entry';
import {mount} from 'enzyme';
import jasmineEnzyme from 'jasmine-enzyme';
describe('QueryHistoryEntry', () => {
let historyWrapper;
beforeEach(() => {
jasmineEnzyme();
});
describe('for a failed query', () => {
beforeEach(() => {
const historyEntry = {
query: 'second sql statement',
start_time: new Date(2016, 11, 11, 1, 33, 5, 99),
status: false,
};
historyWrapper = mount(<QueryHistoryEntry historyEntry={historyEntry}/>);
});
it('displays a pink background color', () => {
expect(historyWrapper.find('div').first()).toHaveStyle('backgroundColor', '#F7D0D5');
});
});
describe('for a successful query', () => {
beforeEach(() => {
const historyEntry = {
query: 'second sql statement',
start_time: new Date(2016, 11, 11, 1, 33, 5, 99),
status: true,
};
historyWrapper = mount(<QueryHistoryEntry historyEntry={historyEntry}/>);
});
it('does not display a pink background color', () => {
expect(historyWrapper.find('div').first()).toHaveStyle('backgroundColor', '#FFF');
});
});
});

View File

@ -10,30 +10,45 @@
import React from 'react'; import React from 'react';
import QueryHistory from '../../../pgadmin/static/jsx/history/query_history'; import QueryHistory from '../../../pgadmin/static/jsx/history/query_history';
import QueryHistoryEntry from '../../../pgadmin/static/jsx/history/query_history_entry'; import QueryHistoryEntry from '../../../pgadmin/static/jsx/history/query_history_entry';
import QueryHistoryDetail from '../../../pgadmin/static/jsx/history/query_history_detail';
import HistoryCollection from '../../../pgadmin/static/js/history/history_collection'; import HistoryCollection from '../../../pgadmin/static/js/history/history_collection';
import jasmineEnzyme from 'jasmine-enzyme'; import jasmineEnzyme from 'jasmine-enzyme';
import {mount, shallow} from 'enzyme'; import {mount} from 'enzyme';
describe('QueryHistory', () => { describe('QueryHistory', () => {
let historyWrapper; let historyWrapper;
beforeEach(() => { beforeEach(() => {
jasmineEnzyme(); jasmineEnzyme();
const historyCollection = new HistoryCollection([]);
historyWrapper = shallow(<QueryHistory historyCollection={historyCollection}/>);
}); });
describe('on construction', () => { describe('on construction, when there is no history', () => {
beforeEach(function () {
const historyCollection = new HistoryCollection([]);
historyWrapper = mount(<QueryHistory historyCollection={historyCollection}/>);
});
it('has no entries', (done) => { it('has no entries', (done) => {
let foundChildren = historyWrapper.find(QueryHistoryEntry); let foundChildren = historyWrapper.find(QueryHistoryEntry);
expect(foundChildren.length).toBe(0); expect(foundChildren.length).toBe(0);
done(); done();
}); });
it('nothing is displayed on right panel', (done) => {
let foundChildren = historyWrapper.find(QueryHistoryDetail);
expect(foundChildren.length).toBe(1);
done();
});
it('does not error', () => {
});
}); });
describe('when it has history', () => { describe('when it has history', () => {
describe('when two SQL queries were executed', () => { describe('when two SQL queries were executed', () => {
let foundChildren; let foundChildren;
let queryDetail;
let historyCollection;
beforeEach(() => { beforeEach(() => {
const historyObjects = [ const historyObjects = [
@ -43,59 +58,114 @@ describe('QueryHistory', () => {
status: false, status: false,
row_affected: 1, row_affected: 1,
total_time: '234 msec', total_time: '234 msec',
message: 'some other message', message: 'message from second sql query',
}, },
{ {
query: 'first sql statement', query: 'first sql statement',
start_time: new Date(2017, 5, 3, 14, 3, 15, 150), start_time: new Date(2017, 5, 3, 14, 3, 15, 150),
status: true, status: true,
row_affected: 2, row_affected: 12345,
total_time: '14 msec', total_time: '14 msec',
message: 'a very important message', message: 'message from first sql query',
}, },
]; ];
const historyCollection = new HistoryCollection(historyObjects); historyCollection = new HistoryCollection(historyObjects);
historyWrapper = mount(<QueryHistory historyCollection={historyCollection}/>); historyWrapper = mount(<QueryHistory historyCollection={historyCollection}/>);
foundChildren = historyWrapper.find(QueryHistoryEntry); foundChildren = historyWrapper.find(QueryHistoryEntry);
queryDetail = historyWrapper.find(QueryHistoryDetail);
}); });
it('has two query history entries', () => { describe('the main pane', () => {
expect(foundChildren.length).toBe(2); it('has two query history entries', () => {
}); expect(foundChildren.length).toBe(2);
it('displays the SQL of the queries in order', () => {
expect(foundChildren.at(0).text()).toContain('first sql statement');
expect(foundChildren.at(1).text()).toContain('second sql statement');
});
it('displays the formatted timestamp of the queries in chronological order by most recent first', () => {
expect(foundChildren.at(0).text()).toContain('Jun 3 2017 14:03:15');
expect(foundChildren.at(1).text()).toContain('Dec 11 2016 01:33:05');
});
it('displays the number of rows affected', () => {
expect(foundChildren.at(1).text()).toContain('1 rows affected');
expect(foundChildren.at(0).text()).toContain('2 rows affected');
});
it('displays the total time', () => {
expect(foundChildren.at(0).text()).toContain('total time: 14 msec');
expect(foundChildren.at(1).text()).toContain('total time: 234 msec');
});
it('displays the truncated message', () => {
expect(foundChildren.at(0).text()).toContain('a very important message');
expect(foundChildren.at(1).text()).toContain('some other message');
});
describe('when there are one failing and one successful query each', () => {
it('adds a white background color for the successful query', () => {
expect(foundChildren.at(0).find('div').first()).toHaveStyle('backgroundColor', '#FFF');
}); });
it('adds a red background color for the failed query', () => {
expect(foundChildren.at(1).find('div').first()).toHaveStyle('backgroundColor', '#F7D0D5'); it('displays the query history entries in order', () => {
expect(foundChildren.at(0).text()).toContain('first sql statement');
expect(foundChildren.at(1).text()).toContain('second sql statement');
});
it('displays the formatted timestamp of the queries in chronological order by most recent first', () => {
expect(foundChildren.at(0).text()).toContain('Jun 3 2017 14:03:15');
expect(foundChildren.at(1).text()).toContain('Dec 11 2016 01:33:05');
});
it('renders the most recent query as selected', () => {
expect(foundChildren.at(0).nodes.length).toBe(1);
expect(foundChildren.at(0).find('QueryHistorySelectedEntry').length).toBe(1);
});
it('renders the older query as not selected', () => {
expect(foundChildren.at(1).nodes.length).toBe(1);
expect(foundChildren.at(1).find('QueryHistoryErrorEntry').length).toBe(1);
});
});
describe('the details pane', () => {
it('displays the formatted timestamp', () => {
expect(queryDetail.at(0).text()).toContain('6-3-17 14:03:15Date');
});
it('displays the number of rows affected', () => {
if (/PhantomJS/.test(window.navigator.userAgent)) {
expect(queryDetail.at(0).text()).toContain('12345Rows Affected');
} else {
expect(queryDetail.at(0).text()).toContain('12,345Rows Affected');
}
});
it('displays the total time', () => {
expect(queryDetail.at(0).text()).toContain('14 msecDuration');
});
it('displays the full message', () => {
expect(queryDetail.at(0).text()).toContain('message from first sql query');
});
it('displays first query SQL', (done) => {
setTimeout(() => {
expect(queryDetail.at(0).text()).toContain('first sql statement');
done();
}, 1000);
});
});
describe('when the older query is clicked on', () => {
beforeEach(() => {
foundChildren.at(1).simulate('click');
});
it('displays the query in the right pane', () => {
expect(queryDetail.at(0).text()).toContain('second sql statement');
});
it('renders the most recent query as selected in the left pane', () => {
expect(foundChildren.at(0).nodes.length).toBe(1);
expect(foundChildren.at(0).find('QueryHistoryVanillaEntry').length).toBe(1);
});
it('renders the older query as selected in the left pane', () => {
expect(foundChildren.at(1).nodes.length).toBe(1);
expect(foundChildren.at(1).find('QueryHistorySelectedErrorEntry').length).toBe(1);
});
});
describe('when a third SQL query is executed', () => {
beforeEach(() => {
historyCollection.add({
query: 'third sql statement',
start_time: new Date(2017, 11, 11, 1, 33, 5, 99),
status: false,
row_affected: 5,
total_time: '26 msec',
message: 'third sql message',
});
});
it('displays third query SQL in the right pane', () => {
expect(queryDetail.at(0).text()).toContain('third sql statement');
}); });
}); });
}); });

View File

@ -828,8 +828,8 @@ bluebird@^3.3.0:
resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.0.tgz#791420d7f551eea2897453a8a77653f96606d67c" resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.0.tgz#791420d7f551eea2897453a8a77653f96606d67c"
bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.1.1, bn.js@^4.4.0: bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.1.1, bn.js@^4.4.0:
version "4.11.6" version "4.11.7"
resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.6.tgz#53344adb14617a13f6e8dd2ce28905d1c0ba3215" resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.7.tgz#ddb048e50d9482790094c13eb3fcfc833ce7ab46"
body-parser@^1.16.1: body-parser@^1.16.1:
version "1.17.2" version "1.17.2"
@ -856,6 +856,10 @@ boom@2.x.x:
dependencies: dependencies:
hoek "2.x.x" hoek "2.x.x"
bowser@^1.6.0:
version "1.7.0"
resolved "https://registry.yarnpkg.com/bowser/-/bowser-1.7.0.tgz#169de4018711f994242bff9a8009e77a1f35e003"
brace-expansion@^1.1.7: brace-expansion@^1.1.7:
version "1.1.8" version "1.1.8"
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.8.tgz#c07b211c7c952ec1f8efd51a77ef0d1d3990a292" resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.8.tgz#c07b211c7c952ec1f8efd51a77ef0d1d3990a292"
@ -1400,6 +1404,12 @@ crypto-browserify@^3.0.0, crypto-browserify@^3.11.0:
public-encrypt "^4.0.0" public-encrypt "^4.0.0"
randombytes "^2.0.0" randombytes "^2.0.0"
css-in-js-utils@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/css-in-js-utils/-/css-in-js-utils-1.0.3.tgz#9ac7e02f763cf85d94017666565ed68a5b5f3215"
dependencies:
hyphenate-style-name "^1.0.2"
css-color-names@0.0.4: css-color-names@0.0.4:
version "0.0.4" version "0.0.4"
resolved "https://registry.yarnpkg.com/css-color-names/-/css-color-names-0.0.4.tgz#808adc2e79cf84738069b646cb20ec27beb629e0" resolved "https://registry.yarnpkg.com/css-color-names/-/css-color-names-0.0.4.tgz#808adc2e79cf84738069b646cb20ec27beb629e0"
@ -2489,6 +2499,10 @@ https-browserify@0.0.1, https-browserify@~0.0.0:
version "0.0.1" version "0.0.1"
resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-0.0.1.tgz#3f91365cabe60b77ed0ebba24b454e3e09d95a82" resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-0.0.1.tgz#3f91365cabe60b77ed0ebba24b454e3e09d95a82"
hyphenate-style-name@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/hyphenate-style-name/-/hyphenate-style-name-1.0.2.tgz#31160a36930adaf1fc04c6074f7eb41465d4ec4b"
iconv-lite@0.4.15, iconv-lite@~0.4.13: iconv-lite@0.4.15, iconv-lite@~0.4.13:
version "0.4.15" version "0.4.15"
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.15.tgz#fe265a218ac6a57cfe854927e9d04c19825eddeb" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.15.tgz#fe265a218ac6a57cfe854927e9d04c19825eddeb"
@ -2561,6 +2575,13 @@ inline-source-map@~0.6.0:
dependencies: dependencies:
source-map "~0.5.3" source-map "~0.5.3"
inline-style-prefixer@^3.0.2:
version "3.0.6"
resolved "https://registry.yarnpkg.com/inline-style-prefixer/-/inline-style-prefixer-3.0.6.tgz#b27fe309b4168a31eaf38c8e8c60ab9e7c11731f"
dependencies:
bowser "^1.6.0"
css-in-js-utils "^1.0.3"
inquirer@^0.12.0: inquirer@^0.12.0:
version "0.12.0" version "0.12.0"
resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-0.12.0.tgz#1ef2bfd63504df0bc75785fff8c2c41df12f077e" resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-0.12.0.tgz#1ef2bfd63504df0bc75785fff8c2c41df12f077e"
@ -4043,7 +4064,7 @@ promise@^7.1.1:
dependencies: dependencies:
asap "~2.0.3" asap "~2.0.3"
prop-types@^15.5.4: prop-types@^15.5.4, prop-types@^15.5.8:
version "15.5.10" version "15.5.10"
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.5.10.tgz#2797dfc3126182e3a95e3dfbb2e893ddd7456154" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.5.10.tgz#2797dfc3126182e3a95e3dfbb2e893ddd7456154"
dependencies: dependencies:
@ -4155,6 +4176,20 @@ react-addons-test-utils@~15.4.2:
loose-envify "^1.1.0" loose-envify "^1.1.0"
object-assign "^4.1.0" object-assign "^4.1.0"
react-split-pane@^0.1.63:
version "0.1.63"
resolved "https://registry.yarnpkg.com/react-split-pane/-/react-split-pane-0.1.63.tgz#fadb3960cc659911dd05ffbc88acee4be9f53583"
dependencies:
inline-style-prefixer "^3.0.2"
prop-types "^15.5.8"
react-style-proptype "^3.0.0"
react-style-proptype@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/react-style-proptype/-/react-style-proptype-3.0.0.tgz#89e0b646f266c656abb0f0dd8202dbd5036c31e6"
dependencies:
prop-types "^15.5.4"
"react@file:../web/pgadmin/static/vendor/react": "react@file:../web/pgadmin/static/vendor/react":
version "15.4.2" version "15.4.2"
dependencies: dependencies:
@ -4184,8 +4219,8 @@ read-pkg@^1.0.0:
path-type "^1.0.0" path-type "^1.0.0"
readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.5, readable-stream@^2.0.6, readable-stream@^2.1.4, readable-stream@^2.1.5, readable-stream@^2.2.6: readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.5, readable-stream@^2.0.6, readable-stream@^2.1.4, readable-stream@^2.1.5, readable-stream@^2.2.6:
version "2.3.0" version "2.3.1"
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.0.tgz#640f5dcda88c91a8dc60787145629170813a1ed2" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.1.tgz#84e26965bb9e785535ed256e8d38e92c69f09d10"
dependencies: dependencies:
core-util-is "~1.0.0" core-util-is "~1.0.0"
inherits "~2.0.3" inherits "~2.0.3"